Tour

Action speaks louder than words ...

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

  编写同步队列时,有用到条件变量,对操作队列的线程进行同步。当队列为空时,允许get线程挂起,直到add线程向队列添加元素并通过唤醒条件变量,get线程继续向下运行。条件变量在多线程程序中用来实现“等待->唤醒”逻辑常用的方法。条件变量要和互斥量相联结,以避免出现条件竞争:一个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件。使用条件变量进行同步时,通常以如下方式进行编码:

 1 pthread_mutex_t mutex;
 2 pthread_cond_t  cond;
 3  
 4 //Get线程
 5 int Get()
 6 {
 7     pthread_mutex_lock(&mutex);
 8     while (queue.empty())
 9     {
10         pthread_cond_wait(&cond, &mutex);
11     }
12     item = queue.front();
13     pthread_mutex_unlock(&mutex);
14     return item;
15 }
16 //Add线程
17 void Add(int item)
18 {
19     pthread_mutex_lock(&mutex);
20     queue.push(item);
21     pthread_cond_signal(&cond);
22     pthread_mutex_unlock(&mutex);
23 }

  pthread_cond_wait函数在把线程放进阻塞队列后,自动对mutex进行解锁,使得其它线程可以获得加锁的权利。注意这是一个原子操作,不会造成条件检查和线程进入休眠状态等待之间有其他线程进入的问题,并使当前线程阻塞在cond参数指向的条件变量上,当此线程因条件不满足而进入休眠后,因为互斥量已经解锁,所以别的线程可以对互斥量加锁并改变临界资源,从而唤醒当前阻塞的线程。Add线程获取到锁之后,队列中添加元素,并发送信号唤醒阻塞在条件变量上的Get线程。当有多个Get线程阻塞在条件变量上时,可能会出现徐假唤醒,即阻塞的多个线程都认为自己已经满足条件了,而实际上可能队列中的元素已经被第一个唤醒的线程Get走了,所以这里必须用while(queue.empty())而不是if(queue.empty())进行判断。

  不知道大家有没有注意到在Add函数中line21和line22,pthread_cond_signal和pthread_mutex_unlock的顺序,即唤醒和解锁的先后顺序是否对程序的运行有何影响。这里简单的分析下,我们知道Add线程向队列中添加元素之后,会调用pthread_cond_signal,将阻塞在Get操作的线程唤醒,那么此时阻塞在pthread_cond_wait() 处的线程将苏醒,并进行返回,这里要注意,并非Add线程调用pthread_cond_signal函数后,pthread_cond_wait() 就立即返回,而是在它获取到mutex并重新lock,才能返回继续向下执行。根据分析想到的结论是,如果先signal再unlock,那么当前阻塞在pthread_cond_wait上的线程可能会立即获取到mutex(因为可能有其他Add线程阻塞在mutex上,所以并不一定获取到mutex),重新lock,返回后向下执行;而如果先unlock再signal,那么解锁后mutex可能已经被其他线程获取到了,那么signal唤醒pthread_cond_wait就会继续等待mutex重新被获取走的线程释放,可能会比前一种情况等待的时间长些,由其他的Add线程将其唤醒。不太确定分析的是否正确,于是在Stack Overflow上看到了同样的问题,建议将signal放在unlock之前,部分翻译总结如下:

  如果Add函数将unlock在前,signal在后,可能会产生Spurious wakeups,考虑如下场景:

  1. 线程A阻塞在Get函数,解锁,等待Add操作向队列中添加item。

  2. 线程B调用Add函数,向队列中添加item。在Add函数unlock之后,还未signal之前,发送了上下文切换。

  3. 线程C获取到mutex,调用Add函数,向队列中添加item,解锁并且调用signal函数。

  4. 此时线程A获取到mutex,wait函数返回,处理了刚才Add进来的两个item,之后继续阻塞在条件变量上。

  5. 此时如果线程B得到CPU时间片,那么继续从2处运行,调用signal,唤醒A线程

  6. 线程A被唤醒,但是因为之前的item已经被它Get出来,所以此时队列仍然为空,所以线程A再次进入阻塞状态。

  参考:

  http://stackoverflow.com/questions/6312342/pthread-cond-wait-and-mutex-requirement

  http://stackoverflow.com/questions/1640389/pthreads-pthread-cond-signal-from-within-critical-section

  http://stackoverflow.com/questions/6419117/signal-and-unlock-order

  http://www.domaigne.com/blog/computing/condvars-signal-with-mutex-locked-or-not/#fig01

posted on 2014-12-18 01:15  Tourun  阅读(2482)  评论(2编辑  收藏  举报