引用APUE中的一句话:linux
Condition variables are another synchronization mechanism available to threads.
These synchronization objects provide a place for threads to rendezvous. When used with mutexes, condition variables allow threads to wait in a race-free way for arbitrary conditions to occur.ide
条件变量是线程的另一种同步机制,这些同步对象为线程提供了会合的场所,理解起来就是两个(或者多个)线程须要碰头(或者说进行交互-一个线程给另外的一个或者多个线程发送消息),咱们指定在条件变量这个地方发生,一个线程用于修改这个变量使其知足其它线程继续往下执行的条件,其它线程则接收条件已经发生改变的信号。函数
条件变量同锁一块儿使用使得线程能够以一种无竞争的方式等待任意条件的发生。所谓无竞争就是,条件改变这个信号会发送到全部等待这个信号的线程。而不是说一个线程接受到这个消息而其它线程就接收不到了。性能
具体的函数介绍就不说了,详细参考APUE,下面经过一个例子来详细说一下正确使用条件变量的方法。下例实现了生产者和消费者模型,生产者向队列中插入数据,消费者则在生产者发出队列准备好(有数据了)后接收消息,而后取出数据进行处理。实现的关键点在如下几个方面:ui
代码:atom
#include <pthread.h> struct msg { struct msg *m_next; /* ... more stuff here ... */ }; struct msg *workq; pthread_cond_t qready = PTHREAD_COND_INITIALIZER; pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER; void process_msg(void) { struct msg *mp; for (;;) { pthread_mutex_lock(&qlock); while (workq == NULL) pthread_cond_wait(&qready, &qlock); mp = workq; workq = mp->m_next; pthread_mutex_unlock(&qlock); /* now process the message mp */ } } void enqueue_msg(struct msg *mp) { pthread_mutex_lock(&qlock); mp->m_next = workq; workq = mp; pthread_mutex_unlock(&qlock); pthread_cond_signal(&qready); }
pthread_cond_wait中的mutex用于保护条件变量,调用这个函数进行等待条件的发生时,mutex会被自动释放,以供其它线程(生产者)改变条件,pthread_cond_wait中的两个步骤必须是原子性的(atomically,万恶的APUE中文版把这个单词翻译成了『自动』,误人子弟啊),也就是说必须把两个步骤捆绑到一块儿:.net
否则呢,若是不是原子性的,上面的两个步骤中间就可能插入其它操做。好比,若是先释放mutex,这时候生产者线程向队列中添加数据,而后signal,以后消费者线程才去『把调用线程放到等待队列上』,signal信号就这样被丢失了。线程
若是先把调用线程放到条件等待队列上,这时候另一个线程发送了pthread_cond_signal(咱们知道这个函数的调用是不须要mutex的),而后调用线程当即获取mutex,两次获取mutex会产生deadlock.翻译
若是不这么作信号可能会丢失,看下面的例子:设计
Thead A Thread B pthread_mutex_lock(&qlock); while (workq == NULL) mp->m_next = workq; workq = mp; pthread_cond_signal(&cond); pthread_cond_wait(&qready, &qlock);
在while判断以后向队列中插入数据,虽然已经有数据了,但线程A仍是调用了pthread_cond_wait等待下一个信号到来。。
while (workq == NULL) pthread_cond_wait(&qready, &qlock); mp = workq;
咱们把while换成if可不能够呢?
if (workq == NULL) pthread_cond_wait(&qready, &qlock); mp = workq;
答案是不能够,一个生产者可能对应着多个消费者,生产者向队列中插入一条数据以后发出signal,而后各个消费者线程的pthread_cond_wait获取mutex后返回,固然,这里只有一个线程获取到了mutex,而后进行处理,其它线程会pending在这里,处理线程处理完毕以后释放mutex,刚才等待的线程中有一个获取mutex,若是这里用if,就会在当前队列为空的状态下继续往下处理,这显然是不合理的。
void enqueue_msg(struct msg *mp) { pthread_mutex_lock(&qlock); mp->m_next = workq; workq = mp; pthread_mutex_unlock(&qlock); pthread_cond_signal(&qready); }
若是先unlock,再signal,若是这时候有一个消费者线程刚好获取mutex,而后进入条件判断,这里就会判断成功,从而跳过pthread_cond_wait,下面的signal就会不起做用;另一种状况,一个优先级更低的不须要条件判断的线程正好也须要这个mutex,这时候就会转去执行这个优先级低的线程,就违背了设计的初衷。
void enqueue_msg(struct msg *mp) { pthread_mutex_lock(&qlock); mp->m_next = workq; workq = mp; pthread_cond_signal(&qready); pthread_mutex_unlock(&qlock); }
若是把signal放在unlock以前,消费者线程会被唤醒,获取mutex发现获取不到,就又去sleep了。浪费了资源.可是在LinuxThreads或者NPTL里面,就不会有这个问题,由于在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。
因此在Linux中推荐使用这种模式。
References:
why pthread_cond_wait need an lock?
Calling pthread_cond_signal without locking mutex
Why do pthreads’ condition variable functions require a mutex?