条件变量(二)

    条件变量是用来等待某个条件是否成立的等待原语。主要有两个函数:

1、 pthread_cond_signal(&cond) :用来发送信号,唤醒正在wait()ing的线程。要搞清楚这里所谓唤醒,要知道其实线程中有两个队列cond_wait和mutex_lock。signal的唤醒只是将cond_wait中的一个线程放入mutex_lock中,而不是回到正常工作状态(所以性能损耗可忽略)。代码中一般在调用signal后,紧接着会解锁,此时mutex_lock队列中的阻塞线程获得互斥锁,上锁。

2、 pthread_cond_wait() :用来等待信号。一般(个人觉得是必须)用while对某个条件进行判断,条件不满足时进入调用pthread_cond_wait()函数。调用wait时先后做了这几个动作:

     解锁-->挂起等待(进入cond_wait,等待signal唤醒)-->被signal唤醒,进入(cond_mutex队列)--重新加锁-

然后进入下一次while循环,判断条件是否还满足。

循环判断的原因如下:两个线程同时调用signal,唤醒两个cond_wait队列中的线程,但是他们存在race condition,两个线程要竞争同一个互斥锁:比如线程1、2竞争,但是线程1竞争到了互斥锁,那么线程2有阻塞了;虽然已经从cond_wait中出来了(并不是从pthread_cond_wait返回);等到线程1释放锁,线程2持有了锁,此时必须循环再检查条件,因为条件已经被线程2修改了。这就是为什么要用while而不是if的原因。避免wait从队列中出来、条件被别的线程修改了。如果是if就造成了虚假唤醒(suprious wake)。为避免这点,必须醒来后再次判断睡眠条件。

下面是一段简单的多线程代码:

 1 pthread_mutex_t m=PTHREAD_MUTEX_INITIALIZER;
 2 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 3 bool signaled =false;
 4 
 5 void waiter()
 6 {
 7     pthread_mutex_lock(&m);
 8     while(!signaled)            //一定要是while,而不是if
 9         pthread_cond_wait(&cond, &m);
10 
11     dosomething(x,y,...);
12     pthread_mutex_unlock(&m);
13 }
14 
15 void signal()
16 {
17     pthread_mutex_lock(&m);
18     signaled = true;            //先改变条件,再调用signal
19     pthread_cond_signal(&cond);
20     pthread_mutex_unlock(&m);
21 }

至于为什么在被唤醒之后还要再次进行条件判断(即为什么要使用while循环来判断条件),是因为可能有“惊群效应”。有人觉得此处既然是被唤醒的,肯定是满足条件了,其实不然。如果是多个线程都在等待这个条件,而同时只能有一个线程进行处理,此时就必须要再次条件判断,以使只有一个线程进入临界区处理。

至于先改变条件值,再调用pthread_cond_signal是因为如果先调用pthread_cond_signal唤醒pthread_cond_wait线程,而此时还没来得及改变signaled,此时wait线程再次while判断signaled的值,发现条件不满足,再次陷入pthread_cond_wait()。这就叫做事件丢失。我在github上问过陈硕大神https://github.com/chenshuo/recipes/issues/18,问他通知和修改是不是写反了,他说持有锁的情况下这么写是正确的,不会导致事件丢失。代码如下:

1 void broadcast()
2 {
3     pthread_mutex_lock(&mutex_);
4     pthread_cond_broadcast(&cond_);
5     signaled_ = true;
6     pthread_mutex_unlock(&mutex_);
7 }

从上文可以看出: 
1,pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,while循环的意义就体现在这里了,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程. 
2,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.所以强烈推荐此处使用while循环. 

       其实说白了很简单,就是pthread_cond_signal()也可能唤醒多个线程,而如果你同时只允许一个线程访问的话,就必须要使用while来进行条件判断,以保证临界区内只有一个线程在处理。 

原文地址:https://www.cnblogs.com/howo/p/8577345.html