pthread.h 相关函数使用方法集锦之线程同步变量
前言
pthread(POSIX thread),简称为pthread,是线程的POSIX标准,在类Unix操作系统中(Unix、Linux、Mac OS X等),都是用pthread作为操作系统的线程。<pthread.h>作为其编程标准的头文件,本文探讨里面的常用函数意义以及使用方法。
在多线程编程中,操作系统引入了锁机制。通过锁机制,能够保证在多核多线程环境中,在某一个时间点上,只能有一个线程进入临界区代码,从而保证临界区中操作数据的一致性。
在pthread里面实现了5中同步变量,分别是互斥锁(mutex)、自旋锁(spinlock)、条件变量(condition)、读写锁(rwlock)以及栅栏(barrier)。
线程同步变量
pthread互斥锁(mutex)
互斥锁是一个二元变量,其状态为开锁(允许0)和上锁(禁止1),将某个共享资源与某个特定互斥锁在逻辑上绑定(要申请该资源必须先获取锁)。
访问公共资源前,必须申请该互斥锁,若处于开锁状态,则申请到锁对象,并立即占有该锁,以防止其他线程访问该资源;如果该互斥锁处于锁定状态,则阻塞当前线程。
只有锁定该互斥锁的进程才能释放该互斥锁,其他线程试图释放无效。互斥锁使用的非常广泛,在所有线程同步变量中,可以重点关注。
pthread_mutex_t
锁类型,定义互斥锁。
pthread_mutex_init
初始化互斥锁。
pthread_mutex_lock
申请互斥锁并占有互斥锁,其他线程无法访问该资源。
pthread_mutex_trylock
pthread_mutex_unlock
释放互斥锁,此时其他线程可以访问该资源。
pthread_mutex_destroy
释放锁资源。
pthread条件变量(condition)
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
pthread_cond_t
pthread_cond_init
pthread_cond_signal
pthread_cond_wait
pthread_cond_broadcast
pthread_cond_wait的使用
pthread_cond_wait总是和一个互斥量同时使用的,我们看看APUE
中关于pthread_cond_wait使用的原话:
传递给pthread_cond_wait的互斥量对条件(condition)进行保护。调用者把锁住的互斥量传给函数,函数然后自动把调用线程放在等待条件(condition)的线程列表上,对互斥量进行解锁。这就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,线程就不会错过条件(condition)的任何变化。
这段话说明了为什么需要互斥量来保护条件变量,因为条件检查和线程进入休眠状态等待条件改变这两个操作都不是线程安全的,所以需要互斥量进行保护。
pthread_mutex_lock(&mutex);
pthread_cond_wait(&condition, &mutex);
pthread_mutex_unlock(&mutex);
我们再来看看互斥量传参进入pthread_cond_wait时,pthread_cond_wait对互斥量做了什么操作。再来回味一下上面APUE
的这段话:
调用者把锁住的互斥量传给函数,函数然后自动把调用线程放在等待条件(condition)的线程列表上,对互斥量进行解锁。
也就是pthread_cond_wait里面做了两个操作,第一个操作是将调用线程放在等待条件的线程列表上;第二个操作是将互斥量解锁。
注意,将互斥量解锁以后pthread_cond_wait并没有返回,而是等待条件成立,等条件成立时,pthread_cond_wait会再次获得互斥量。
所以pthread_cond_wait对互斥量的操作实际上是这样子的。
| -- pthread_mutex_lock lock 互斥量 (1)
|
| -- 进入pthread_cond_wait函数逻辑
| -- unlock 互斥量 (2)
|
| -- 等待条件成立
|
| -- lock 互斥量 (3)
|
| -- 退出pthread_cond_wait函数逻辑
|
| -- pthread_mutex_unlock unlock 互斥量 (4)
在这个过程中,互斥量实际上是已经被锁住了两次。
我们编写一个死锁的例子举证一下上面的过程,在这个例子我们验证pthread_cond_wait是否会lock互斥量,进入pthread_cond_wait时,互斥量是没有锁住的,在pthread_cond_wait返回之后,别的线程尝试获取锁,如果卡死,那便说明pthread_cond_wait里面有lock互斥量的操作。
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* test(void *p){
pthread_mutex_lock(&mutex); // (1)
pthread_mutex_unlock(&mutex); // (4)
cout << "child thread wait condition. " << endl;
pthread_cond_wait(&cond, &mutex); // (2) (3)
cout << "child thread get condition. " << endl;
cout << "child thread return." << endl;
// pthread_mutex_unlock(&mutex);
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, test, NULL);
sleep(1); // 保证子线程先启动
cout << "main thread signal condition. " << endl;
pthread_cond_signal(&cond);
sleep(1); // 保证子线程能够先得到收到条件并处理完
cout << "main thread acquire lock. " << endl;
pthread_mutex_lock(&mutex); // 尝试获取锁
cout << "main thread acquire lock success. " << endl;
}
/* =========================================================================
* 输出
*
* child thread wait condition.
* main thread signal condition.
* child thread get condition.
* child thread return.
* main thread acquire lock.
*
* =========================================================================
*/
在子线程返回后,主线程会一直卡在acquire lock这里,不会success,说明在pthread_cond_wait里面确实有加锁的操作,此时如没有释放锁,其他线程获取锁将会进入死锁状态,所以在pthread_cond_wait使用前后,一定要使用pthread_mutex_lock和pthread_mutex_unlock。
pthread自旋锁(spinlock)
自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
pthread_spinlock_t
pthread_spin_init
pthread_spin_destroy
pthread_spin_lock
pthread_spin_trylock
pthread_spin_unlock
pthread读写锁(rwlock)
读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。
pthread_rwlock_t
pthread_rwlock_init
pthread_rwlock_wrlock
pthread_rwlock_rdlock
pthread_rwlock_unlock
pthread_rwlock_tryrdlock
pthread_rwlock_trywdlock
pthread_rwlock_destroy
pthread栅栏(barrier)
栅栏(Barrier)是并行计算中的一种同步方法。对于一群进程或线程,程序中的一个同步屏障意味着任何线程/进程执行到此后必须等待,直到所有线程/进程都到达此点才可继续执行下文。