条件变量(Condtion Variable)是在多线程程序中用来实现“等待->唤醒”逻辑常用的方法。
举个实例场景:
一个进程p中有两个线程A和B。A运行到某处后,需要等到bool变量flag变为true后才能继续运行,而设置flag的值为true这个操作需要线程B来完成,这种情况我们如何来实现?
- 第一种:线程A在检测flag之前调用pthread_join等待线程B终止,然后检查flag的值,若为true,则继续执行后段(当然,这种方式很不实用,一定要等到线程B终止,耗费时间巨大)
- 第二种:线程A定时轮询flag,如果flag为false,则继续休眠,如果为true,则开始执行
- 第三种:使用条件变量,A在flag为false时调用cond_wait进行等待,B在改变flag的值后,调用cond_signal,唤醒在等待中的A,告诉A flag的值变了,这样A便可继续往下执行。
#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); 两者的返回值都是:若成功则返回0,否则返回错误号
/* 等待条件变为真 */ int pthread_cond_wait (pthread_cond_t *cond,pthread_mutex_t *mutex); /* 限时等待条件为真 */ int pthread_cond_timedwait (pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime);传递给pthread_cond_wait的互斥量对条件进行保护。调用者把锁住的互斥量传给函数,函数然后把调用线程放到等待条件的线程列表中,对互斥量解锁。pthread_cond_wait返回时,互斥量再次被锁住。
#include<pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
in pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_wait()用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。pthread_cond_wait()必须与pthread_mutex 配套使用。
pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread_cond_signal()或pthread_cond_broadcast,把该线程唤醒,使pthread_cond_wait()通过(返回)时,
该线程又自动获得该mutex。
pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
使用pthread_cond_signal一般不会有“惊群现象”产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那 么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一 个pthread_cond_signal调用最多发信一次。
但是pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程.
另外,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.所以强烈推荐对pthread_cond_wait()使用while循环来做条件判断.
以下代码摘自unix环境高级编程
#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); }
为什么使用while而不是if
while (...) pthread_cond_wait(&qready,&qlock);
1.假设仓库为空,有2个消费者在等待商品,设为C1和C2
2.假设生产者只生产了1件商品,然后调用pthread_cond_broadcast,则C1和C2都会得到通知
3.假设C1比C2先得到通知,然后加锁把商品消费了,并且解锁,这时C2就能拿到锁,但是此时商品已经没有了,如果此时C2不做检测,则会出现数据同步问题