天天看点

Pthread多线程入门学习笔记前言线程创建JoiningStack Management其它函数Mutex Variable(互斥锁)Condition Variables

文章目录

  • 前言
  • 线程创建
    • 案例
    • 案例解析
  • Joining
    • Joining的一般过程
    • 案例
    • 案例解析
  • Stack Management
    • 案例
  • 其它函数
  • Mutex Variable(互斥锁)
    • Creating and Destroying Mutexes and Its Attributes
    • Locking and Unlocking Mutexes
  • Condition Variables
    • Creating and Destroying Condition Variables
    • Waiting and Signaling on Condition Variables
未被指明返回值意图的都是:If successful, these functions return 0. Otherwise, an error number is returned to indicate the error.

前言

  • 本文不能起到pthread多线程、并行、线程与进程入门教学的作用,请需要pthread入门教学的同学出门百度:
    • POSIX Threads Programming(一份入门文献,非常简单易懂)
    • Multithreaded Programming (POSIX pthreads Tutorial)
    • POSIX thread (pthread) libraries
    • unix环境高级编程
  • 本文不面向Windows端多线程。

线程创建

  • 线程创建函数
  • pthread_create(*thread, *attr, *start_routine, (void*)arg)

  • thread

    pthread_t

    类型变量的指针,用于存储线程的identifier。id由

    pthread_create

    给予
  • attr

    :控制线程属性变量的地址,后面讲解
  • start_routine

    :线程的入口函数地址。该入口函数原型:
    • void* start_routine(void* arg)

  • arg

    :传给

    start_routine

    的参数,务必是void* 类型,在线程入口函数内部转换为其它类型。

案例

#include <iostream>
#include <pthread.h>

#define NUM_OF_THREADS 5

using namespace std;

void* test(void* pi)
{
	printf("The %d thread\n", *(int*)pi);
	pthread_exit(NULL);
}

int main(int argc, char const *argv[])
{
	pthread_t threads[NUM_OF_THREADS];
	int rc;

	for(auto i = 0; i < NUM_OF_THREADS; ++i)
	{
		cout << "creating the " << i << " thread" << endl;

		rc = pthread_create(&threads[i], NULL, test, (void*)&i);
		if(rc)
		{
			cout << "ERROR " << rc << endl;
			return 0;
		}
		cout << "threads ID " << threads[i] << endl;
	}

	pthread_exit(NULL);
}

//运行结果:
creating the 0 thread
threads ID 0x700009f7f000
creating the 1 thread
threads ID 0x70000a002000
creating the 2 thread
The 2 thread
The 1 thread
threads ID 0x70000a085000
creating the 3 thread
The 3 thread
threads ID 0x70000a108000
creating the 4 thread
The 3 thread
threads ID 0x70000a18b000
The 5 thread

           

案例解析

  • 多个线程之间是并行关系,并没有前后串行约束,因此我们会看到输出结果以非串行的方式出现,可能有的线程快有的线程慢。新建的线程不一定比主线程慢。
  • 在主函数最后使用

    pthread_exit()

    而不是

    return 0

    可以有效避免:主线程在其它线程还没有完成它们的工作时强行结束整个进程,导致有的工作尚未被完成。若主线程调用该函数,主线程将会等待其它线程完成工作后再结束进程。
  • rc = pthread_create(&threads[i], NULL, test, (void*)&i);

    有致命伤:传入了参数i的地址,这样该地址指向的内存被所有线程共享。由于线程的时序关系是并行的,有可能本来想要传入

    i = 2

    ,线程入口函数调用的时候i已经是3了。
This example performs argument passing incorrectly. It passes the address of variable i, which is shared memory space and visible to all threads. As the loop iterates, the value of this memory location changes, possibly before the created threads can access it.
  • 正确写法:
#include <iostream>
#include <pthread.h>

#define NUM_OF_THREADS 5

using namespace std;

void* test(void* dynamic_pi)
{
	printf("The %d thread\n", *(int*)dynamic_pi);
	pthread_exit(NULL);
}

int main(int argc, char const *argv[])
{
	pthread_t threads[NUM_OF_THREADS];
	int rc;

	for(auto i = 0; i < NUM_OF_THREADS; ++i)
	{
		cout << "creating the " << i << " thread" << endl;

		//修改1:让i的内容存储在不同的地址空间中,避免线程互相干扰
		//对方的内存
		int* j = new int;
		*j = i;
		
		//修改2
		rc = pthread_create(&threads[i], NULL, test, (void*)j);
		if(rc)
		{
			cout << "ERROR " << rc << endl;
			return 0;
		}

		cout << "threads ID " << threads[i] << endl;
	}

	pthread_exit(NULL);
}

//运行结果:
creating the 0 thread
threads ID 0x7000023a7000
creating the 1 thread
The 0 thread
threads ID 0x70000242a000
creating the 2 thread
The 1 thread
threads ID 0x7000024ad000
creating the 3 thread
The 2 thread
threads ID 0x700002530000
creating the 4 thread
The 3 thread
threads ID 0x7000025b3000
The 4 thread

           

Joining

  • The pthread_join() function suspends execution of the calling thread until the target thread terminates unless the target thread has already terminated.
  • pthread_attr_t

    :pthread中的attribute变量类型
  • pthread_attr_init(*attr)

    :初始化attribute变量
  • pthread_attr_setdetachstatus(*attr, STATUS)

    :设置attribute的状态值。若需要使用attr将thread的属性初始化为joinable,则

    STATUS == PTHREAD_CREATE_JOINABLE

    ,STATUS是常量
  • pthread_exit(void* status)

    :线程终止函数,status和pthread_join有关
  • pthread_join(thread_id, void** status)

    :thread_id是pthread_create成功创建新进程后,存储在第一个参数指向的位置中的内容。status是个二级指针,指向的内存存储一个一级指针,这个一级指针是thread_id对应的线程中调用的

    pthread_exit()

    的参数。
  • pthread_attr_destroy(*attr)

    :释放attr指向的内存空间

Joining的一般过程

  • Declare a pthread attribute variable of the

    pthread_attr_t

    data type
  • Initialize the attribute variable with

    pthread_attr_init()

  • Set the attribute detached status with

    pthread_attr_setdetachstate()

  • pthread_create()

    的第二个属性参数使用上面定义的

    attr

    ,将被创建的thread的属性置为joinable的,这样后续线程需要join该线程的时候才能成功join上。
  • 使用

    pthread_join()

    join目标线程。这样calling thread of

    pthread_join()

    会挂起自己直到线程运行完毕。
  • When done, free library resources used by the attribute with

    pthread_attr_destroy()

案例

  • 如果需要线程在某个时段串行运行(例如多个线程连续多次对同一个内存空间进行操作),可以使用joining来挂起线程,例如:
#include <iostream>
#include <pthread.h>

#define NUM_OF_THREADS 5
using namespace std;

void* test(void* longp_i)
{
	printf("The %ld thread\n", *(long*)longp_i);
	pthread_exit(longp_i);
	//这里将longp_i作为状态值返回,该状态值会存储在pthread_join()第二个参数
	//指向的内存空间
}

int main(int argc, char const *argv[])
{
	pthread_t threads[NUM_OF_THREADS];
	long* longp_status;
	int rc;

	pthread_attr_t attr;//attribute变量
	pthread_attr_init(&attr);//初始化attribute变量
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
	//设置attributeb变量属性为joinable

	for(long i = 0; i < NUM_OF_THREADS; ++i)
	{
		cout << "creating the " << i << " thread" << endl;

		long* longp_i = &i;
		rc = pthread_create(&threads[i], &attr, test, (void*)longp_i);
		//设置新建线程属性为attribute变量指定的属性——joinable
		if(rc)
		{
			cout << "ERROR by pthread_create() " << rc << endl;
			pthread_exit(NULL);
		}
		
		long** longpp_status = &longp_status;
		rc = pthread_join(threads[i], (void**)longpp_status);
		//主线程(calling thread)join上线程thread[i],这是线程id,
		//由pthread_create()建立新线程后存储在第一个参数指向的内存空间中
		// On return from a successful pthread_join() call 
		// with a non-NULL value_ptr argument, the value passed to 
		// pthread_exit() by the terminating thread is stored in the 
		// location referenced by value_ptr. 
		if(rc)
		{
			cout << "ERROR by pthread_join() " << rc << endl;
			pthread_exit(NULL);
		}
		printf("Status is %ld\n", *longp_status);
	}

	pthread_attr_destroy(&attr);
	//释放attr的空间当不需要attr的时候
	pthread_exit(NULL);
}
}

//运行结果:
creating the 0 thread
The 0 thread
creating the 1 thread
The 1 thread
creating the 2 thread
The 2 thread
creating the 3 thread
The 3 thread
creating the 4 thread
The 4 thread

           

案例解析

  • 上述代码可以解决某个时段,主线程需要生成的其它线程串行地对某个内存空间进行修改,就可以在某个生成线程开始运作是suspense自己,等待它对该内存空间修改结束后再继续运行,并创建下一个线程修改该内存空间。循环往复。
  • 我在敲这部分代码时发现,由于各个pthread API的参数类型不是很统一,变量或一级指针或二级指针我经常会搞混,而且有的可以直接用原数据类型有的就必须是void*或者void**。类型过于混乱,故我在声明变量名的时候前面加上了类型前缀,防止混淆。这些类型前缀都是原类型。
这时候匈牙利命名法似乎很有用?
  • 观察这些函数原型,参数都采用了传指针而不是传值。当calling thread向被thread内传指针,可以避免像传值那样开辟一份新的内存空间;当被调用thread需要将某个参数传回给calling thread时,若传值,则数据只会被保存在参数的一个临时变量中,在被调用thread结束后丢失;若传指针则不会有这样的问题。
  • 这些函数原型提醒我们传指针的关键意义。

Stack Management

  • pthread_attr_getstacksize (*attr, *stacksize)

    :获取在新建线程时默认的栈大小,存储在stacksize指向的内存空间中。
  • pthread_attr_setstacksize (*attr, *stacksize)

    :将指定的栈大小存储在stacksize指向的内存空间中,将该指针与属性attr指向的属性变量绑定,然后在pthread_create新的进程时在第二个参数(属性)传入attr,即可调整新建的thread拥有的栈大小。
  • pthread_attr_getstackaddr (*attr, *stackaddr)

    :获取栈底地址
  • pthread_attr_setstackaddr (*attr, *stackaddr)

    :设置栈底地址

pthread_attr_getstackaddr (*attr, *stackaddr)

and

pthread_attr_setstackaddr (*attr, *stackaddr)

较为少用。对stack起始的直接调整容易引发内存泄漏错误。
  • stacksize

    是size_t类型的变量,

    stackaddr

    类型较为复杂这里暂不讨论

案例

#include <pthread.h>
#include <iostream>

using namespace std;

int main(int argc, char const *argv[])
{
    pthread_attr_t attr;
    size_t stacksize;
    void * _Nullable * _Nonnull stackaddr;

    pthread_attr_init(&attr);

    pthread_attr_getstacksize(&attr, &stacksize);
    cout << stacksize << endl;
    
    return 0;
}
           

其它函数

  • pthread_t pthread_self(void)

    : return the calling thread’s ID
  • int pthread_equal(pthread_t t1, pthread_t t2)

    : 比较两个pthread ID是否相等。若相等则返回非0值,不相等则返回0值

Mutex Variable(互斥锁)

  • Mutex = mutual exclusion = 互斥
  • A typical sequence in the use of a mutex is as follows:
    1. Create and initialize a mutex variable
    2. Several threads attempt to lock the mutex
    3. Only one succeeds and that thread owns the mutex
    4. The owner thread performs some set of actions
    5. The owner unlocks the mutex
    6. Another thread acquires the mutex and repeats the process
    7. Finally the mutex is destroyed

Creating and Destroying Mutexes and Its Attributes

  • pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

  • int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* m_attr)

    : m_attr为mutex的属性变量地址,用于将属性与互斥锁绑定。If successful, pthread_mutex_init() will return zero and put the new mutex id into mutex, otherwise an error number will be returned to indicate the error。
  • int pthread_mutex_destroy(pthread_mutex_t *mutex)

    : 销毁一个mutex并释放内存空间。If successful, pthread_mutex_destroy() will return zero, otherwise an error number will be returned to indicate the error.
The mutex is initially unlocked.
  • pthread_mutexattr_t attr

  • int pthread_mutexattr_init(pthread_mutexattr_t *attr)

  • int pthread_mutexattr_destroy(pthread_mutexattr_t* attr)

Locking and Unlocking Mutexes

  • int pthread_mutex_lock(pthread_mutex_t *mutex)

    : 申请acquire a lock on the specified mutex variable. If the mutex is already locked by another thread, this call will block the calling thread until the mutex is unlocked.
  • int pthread_mutex_trylock(pthread_mutex_t *mutex)

    : 与上述函数同样是申请一个互斥锁,但若申请不成功,不会死锁等待。
  • int pthread_mutex_unlock(pthread_mutex_t *mutex)

    : An error will be returned if:
    1. If the mutex was already unlocked
    2. If the mutex is owned by another thread

Condition Variables

  • While mutexes implement synchronization by controlling thread access to data, condition variables allow threads to synchronize based upon the actual value of data.
  • A representative sequence for using condition variables is shown below.
    1. Declare and initialize global data/variables which require synchronization (such as “count”)
    2. Declare and initialize a condition variable object
    3. Declare and initialize an associated mutex
    4. Create threads A and B to do work

Creating and Destroying Condition Variables

与互斥锁的API类似,这里不做详细介绍
  • pthread_cond_t condition

  • int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr)

  • int pthread_cond_destroy(pthread_cond_t *cond)

  • pthread_condattr_t attr

  • int pthread_condattr_init(pthread_condattr_t *attr)

  • int pthread_condattr_destroy(pthread_condattr_t *attr)

Waiting and Signaling on Condition Variables

  • int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)

    : pthread_cond_wait() blocks the calling thread until the specified condition is signalled. This routine should be called while mutex is locked( for use by other threads) , and it will automatically release the mutex while it waits. After signal is received and thread is awakened, mutex will be automatically locked for use by the thread. The programmer is then responsible for unlocking mutex when the thread is finished with it.
  • int pthread_cond_signal(pthread_cond_t *cond)

    : The pthread_cond_signal() routine is used to signal (or wake up) another thread which is waiting on the condition variable. It should be called after mutex is locked, and must unlock mutex in order for pthread_cond_wait() routine to complete.
  • int pthread_cond_broadcast(pthread_cond_t *cond)

    : 若多个线程都在wait for condition variable

    cond

    ,则应当通过该API而不是上一个API来signal

    cond

不能在调用wait之前调用signal
  • wait和signal的基本关系是:
    1. 在mutex被其它线程locked时,本线程调用wait,自动释放mutex对本线程的lock,并进入等待signal状态(睡眠)。
    2. 某线程调用signal(signal

      cond

      ),则本线程被唤醒,自动lock mutex
    3. signal调用完后需要unlock mutex of 本线程,本线程的wait才会执行完毕,开始完成wait后面的工作。
    4. 本线程所有工作完成后,需手动lock mutex