pthread 的条件变量

通常而言,阻塞一个线程要使用互斥锁 mutex ,但是在特别情况下则可能需要别的选择
比如,当需要等一个线程的中间执行结果时,如果使用互斥锁,那么只能写一个无限循环,并且在循环内不断加锁、解锁,这显然是一个很不优雅的写法

这时可以使用条件变量pthread_cond来解决这个问题

在一个线程调用int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);阻塞该线程,直至条件满足
而在工作线程中,则使用int pthread_cond_signal(pthread_cond_t *cv);通知阻塞的线程继续执行

这里的pthread_cond_tpthread_mutex_t本质上都是一个锁,可以看作是在 A 线程阻塞,当 B 线程执行结束后解锁该锁,从而使得 A 线程继续执行

pthread_cond_wait

int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);会阻塞当前线程,等待其他线程的回应
需要注意的是,这个函数执行结束不代表条件一定满足条件,因为条件变量本身也只是一个锁,并不存在任何判断。同时在多核处理器中,也有可能因为各种问题解锁条件变量1。因此,必须在阻塞的线程上判断条件是否符合,也即阻塞的部分应该类似这个样子:

pthread_mutex_lock(&mutex);
while (ok) {
    pthread_cond_wait(&cv, &mutex);
}
pthread_mutex_unlock(&mutex);

在这里,因为需要在多个进程中都需要对判断的条件涉及的变量进行读写,因此需要使用一个单独的互斥锁对其进行保护。
这也是为什么阻塞函数必须要传入一个互斥锁:需要一个互斥锁对判断条件进行保护2 3

而这个阻塞函数本身要完成的任务如下:

  1. 释放互斥锁,以供子线程中使用
  2. 将条件变量锁加入到队列中,等待触发
  3. 条件变量锁触发,给互斥锁加锁

pthread_cond_signal 和 pthread_cond_broadcast

int pthread_cond_signal(pthread_cond_t *cv);会发送信号,解锁该条件变量阻塞的一个线程(具体顺序看调用队列)
int pthread_cond_broadcast(pthread_cond_t *cv);会发送信号,解锁该条件变量阻塞的所有线程

如果cv对应的条件变量并没有阻塞线程,那么该函数效果相当于没有执行

同时,通常情况而言,调用该函数前需要判断一个条件是否满足,因此对于该条件需要加锁,并且需要是阻塞的线程传入的互斥锁。但是在实际使用中,如果这个条件涉及的变量只在阻塞的线程和工作线程中用到,那么实际上在工作线程里判断条件没有必要加锁4。当然,加了除了可能会影响性能也没有别的问题

参考资料


  1. Linux下Condition Vairable和Mutext合用的小细节 ↩︎

  2. 为什么pthread_cond_wait需要互斥锁mutex作为参数 ↩︎

  3. pthread_cond_wait 为什么需要传递 mutex 参数? ↩︎

  4. 详解linux互斥锁 pthread_mutex和条件变量pthread_cond ↩︎