条件变量
互斥锁可以用来同步线程对共享数据的访问,条件变量则是用于在线程之间同步共享数据的值。条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。条件变量是在多个线程需要交互的情况下,用来线程间同步的原语。
如果需要等待某个条件成立,就应该使用条件变量。
条件变量的相关函数主要有如下5个:
#include <pthread.h>
int pthread_cond_init(pthread_cond_t* cond, const pthread_condattr_t* cond_attr);
int pthread_cond_destroy(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
这些函数的第一个参数cond指向要操作的目标条件变量,条件变量的类型是pthread_cond_t结构体。
pthread_cond_init 函数用于初始化条件变量。cond_attr参数指定条件变量的属性。如果将它设置为NULL,则表示使用默认属性。除了使用该函数,也可以用宏来进行初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
宏PTHREAD_COND_INITIALIZER实际上只是把条件变量的各个字段都初始化为0。
pthread_cond_destroy 函数用于销毁条件变量,以释放其占用的内核资源。销毁一个正在被等待的条件变量将失败并返回EBUSY。
pthread_cond_broadcast 函数以广播的方式唤醒所有等待目标条件变量的线程。
pthread_cond_signal 函数用于唤醒一个等待目标条件变量的线程。至于哪个线程将被唤醒,则取决于线程的优先级和调度策略。
pthread没有提供一个直接的API用来唤醒一个指定的线程,所以,如果想要唤醒一个指定的线程的话,需要间接地实现:定义一个能够唯一表示目标线程的全局变量,在唤醒等待条件变量的线程前,先设置该全局变量为目标线程,然后采用广播的方式唤醒所有等待条件变量的线程,这些线程被唤醒后都检查该全局变量以判断被唤醒的是否是自己(等待条件变量的函数调用是放在while循环中的),如果是就开始执行后续代码,如果不是则返回继续等待。
pthread_cond_wait 函数用于等待目标条件变量。mutex 参数是用于保护条件变量的互斥锁,以确保 pthread_cond_wait 操作的原子性。在调用 pthread_cond_wait 前,必须确保互斥锁 mutex 已经加锁,否则将导致不可预期的结果。pthread_cond_wait 函数执行时, 首先把调用线程放入条件变量的等待队列中,然后将互斥锁mutex解锁。可见,从pthread_cond_wait 开始执行到其调用线程被放入条件变量的等待队列之间的这段时间内,pthread_cond_signal和pthread_cond_broadcast 等函数不会修改条件变量,即pthread_cond_wait函数不会错过目标条件变量的任何变化。pthread_cond_wait函数成功返回时,互斥锁mutex将再次被锁上。
上面这些函数成功时返回0,失败时返回错误码。
对于条件变量只有一种正确的使用方式
在wait端:
- 必须与mutex一起使用,进行判断的布尔表达式的读写需要受此mutex保护。
- 在mutex已经上锁的时候才能调用 wait()。
- 把判断布尔条件和 wait() 放到while循环中。
为什么必须用while循环,而不能用if?
这是因为需要避免 spurios wakeup,即虚假唤醒。
当线程从等待已发出信号的条件变量中醒来,却发现它等待的条件未得到满足时,就会发生虚假唤醒。之所以称为虚假,是因为该线程似乎无缘无故地被唤醒了。不过,这通常是因为在发出条件变量信号和之前调用wait的线程最终运行之间,另一个线程运行并更改了条件。比如,如果有多个线程在等待一个条件变量,系统可能会通过 broadcast() 的方式将它们全部唤醒,从而打破了信号和唤醒之间1:1的关系,如果有10个线程在等待,经过竞争,只有1个线程会获胜,另外9个会经历虚假唤醒。
因此,当线程在条件变量上被唤醒时,它应该始终检查它所寻求的条件是否得到满足。如果不是,那么它应该重新调用wait(),等待下一次唤醒。如果采用if的话,当线程被唤醒时,会直接执行之后的代码,而不会再次检查之前的条件是否满足。