在整理Java LockSupport.park()的东东。看到了个"Spurious wakeup"。又一次梳理下。

首先来个《UNIX环境高级编程》里的样例:

  1. #include <pthread.h>
  2. struct msg {
  3. struct msg *m_next;
  4. /* ... more stuff here ... */
  5. };
  6. struct msg *workq;
  7. pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
  8. pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
  9. void process_msg(void) {
  10. struct msg *mp;
  11. for (;;) {
  12. pthread_mutex_lock(&qlock);
  13. while (workq == NULL)
  14. pthread_cond_wait(&qready, &qlock);
  15. mp = workq;
  16. workq = mp->m_next;
  17. pthread_mutex_unlock(&qlock);
  18. /* now process the message mp */
  19. }
  20. }
  21. void enqueue_msg(struct msg *mp) {
  22. pthread_mutex_lock(&qlock);
  23. mp->m_next = workq;
  24. workq = mp;
  25. pthread_mutex_unlock(&qlock);
  26. pthread_cond_signal(&qready);
  27. }

一个简单的消息生产者和消费者的代码。它们之间用condition同步。

这个代码最easy让人搞混的是process_msg函数里的pthread_mutex_lock 和 pthread_mutex_unlock 是一对函数调用。前面加锁。后面解锁。的确,是加锁解锁。可是它们两不是一对的。它们的还有一半在pthread_cond_wait函数里。

pthread_cond_wait函数能够觉得它做了三件事:

  • 把自身线程放到condition的等待队列里,把mutex解锁;
  • 等待被唤醒(当其他线程调用pthread_cond_signal或者pthread_cond_broadcast时)。
  • 被唤醒之后。对metex加锁。再返回。

mutex和condition实际上是绑定在一起的,一个condition仅仅能相应一个mutex。在Java的代码里,Condition对象仅仅能通过lock.newCondition()的函数来获取。

Spurious wakeup

所谓的spurious wakeup。指的是一个线程调用pthread_cond_signal()。却有可能不止一个线程被唤醒。

为什么会出现这样的情况?wiki和其他的一些文档都仅仅是说在多核的情况下。简化实现同意出现这样的spurious wakeup。

在man文档里给出了一个可能的实现。然后解析为什么会出现。

假定有三个线程,线程A正在运行pthread_cond_wait,线程B正在运行pthread_cond_signal,线程C正准备运行pthread_cond_wait函数。

  1. pthread_cond_wait(mutex, cond):
  2. value = cond->value; /* 1 */
  3. pthread_mutex_unlock(mutex); /* 2 */
  4. pthread_mutex_lock(cond->mutex); /* 10 */
  5. if (value == cond->value) { /* 11 */
  6. me->next_cond = cond->waiter;
  7. cond->waiter = me;
  8. pthread_mutex_unlock(cond->mutex);
  9. unable_to_run(me);
  10. } else
  11. pthread_mutex_unlock(cond->mutex); /* 12 */
  12. pthread_mutex_lock(mutex); /* 13 */
  13. pthread_cond_signal(cond):
  14. pthread_mutex_lock(cond->mutex); /* 3 */
  15. cond->value++; /* 4 */
  16. if (cond->waiter) { /* 5 */
  17. sleeper = cond->waiter; /* 6 */
  18. cond->waiter = sleeper->next_cond; /* 7 */
  19. able_to_run(sleeper); /* 8 */
  20. }
  21. pthread_mutex_unlock(cond->mutex); /* 9 */

线程A运行了第1,2步,这时它释放了mutex。然后线程B拿到了这个mutext,而且pthread_cond_signal函数时运行并返回了。于是线程B就是一个所谓的“spurious wakeup”。

为什么pthread_cond_wait函数里一进入。就释放了mutex?没有找到什么解析。。

查看了glibc的源码,大概能够看出上面的一些影子,可是太复杂了,也没有搞明确为什么。。

/build/buildd/eglibc-2.19/nptl/pthread_cond_wait.c

/build/buildd/eglibc-2.19/nptl/pthread_cond_signal.c

只是从上面的解析,能够发现《UNIX高级编程》里的说明是错误的(可能是由于太久了)。

The  caller passes it locked to the function, which then atomically places the calling thread on the list of threads waiting for the condition and unlocks the mutex.

上面的伪代码,一进入pthread_cond_wait函数就释放了mutex,明显和书里的不一样。

wait morphing优化

在《UNIX环境高级编程》的演示样例代码里,是先调用pthread_mutex_unlock,再调用pthread_cond_signal。

  1. void enqueue_msg(struct msg *mp) {
  2. pthread_mutex_lock(&qlock);
  3. mp->m_next = workq;
  4. workq = mp;
  5. <strong>  pthread_mutex_unlock(&qlock);
  6. pthread_cond_signal(&qready);</strong>
  7. }

有的地方给出的是先调用pthread_cond_signal。再调用pthread_mutex_unlock:

  1. void enqueue_msg(struct msg *mp) {
  2. pthread_mutex_lock(&qlock);
  3. mp->m_next = workq;
  4. workq = mp;
  5. pthread_cond_signal(&qready);
  6. pthread_mutex_unlock(&qlock);
  7. }

先unlock再signal,这有个优点。就是调用enqueue_msg的线程能够再次參与mutex的竞争中,这样意味着能够连续放入多个消息,这个可能会提高效率。

类似Java里ReentrantLock的非公平模式。

网上有些文章说。先singal再unlock,有可能会出现一种情况是被singal唤醒的线程会由于不能立即拿到mutex(还没被释放)。从而会再次休眠,这样影响了效率。从而会有一个叫“wait morphing”优化,就是假设线程被唤醒可是不能获取到mutex,则线程被转移(morphing)到mutex的等待队列里。

可是我查看了下glibc的源码,貌似没有发现有这样的“wait morphing”优化。

man文档里提到:

The pthread_cond_broadcast() or pthread_cond_signal() functions may be called by a thread whether or not it currently owns the mutex that  threads  calling  pthread_cond_wait()  or pthread_cond_timedwait() have associated with the condition variable
during their waits; however, if predictable scheduling behavior is required, then that mutex shall be locked by the thread calling pthread_cond_broadcast() or pthread_cond_signal().

可见在调用singal之前。能够不持有mutex。除非是“predictable scheduling”,可预測的调度行为。这样的可能是实时系统才有这样的严格的要求。

为什么要用while循环来推断条件是否成立?

  1. while (workq == NULL)
  2. pthread_cond_wait(&qready, &qlock);

而不用if来推断?

  1. if (workq == NULL)
  2. pthread_cond_wait(&qready, &qlock);

一个原因是spurious wakeup,但即使没有spurious wakeup。也是要用While来推断的。

比方线程A,线程B在pthread_cond_wait函数中等待,然后线程C把消息放到队列里,再调用pthread_cond_broadcast。然后线程A先获取到mutex。处理完消息完后,这时workq就变成NULL了。这时线程B才获取到mutex,那么这时实际上是没有资源供线程B使用的。所以从pthread_cond_wait函数返回之后,还是要推断条件是否成功。假设成立,再进行处理。

pthread_cond_signal和pthread_cond_broadcast

在这篇文章里,http://www.cppblog.com/Solstice/archive/2013/09/09/203094.html

给出的演示样例代码7里,觉得调用pthread_cond_broadcast来唤醒全部的线程是比較好的写法。可是我觉得pthread_cond_signal和pthread_cond_broadcast是两个不同东东,不能简单合并在同一个函数调用。

仅仅唤醒一个效率和唤醒全部等待线程的效率显然不能等同。典型的condition是用CLH或者MCS来实现的,要通知全部的线程,则要历遍链表,显然效率减少。另外。C++11里的condition_variable也提供了notify_one函数。

http://en.cppreference.com/w/cpp/thread/condition_variable/notify_one

mutex,condition是不是公平(fair)的?

这个在參考文档里没有说明。在网上找了些资料,也没有什么明白的答案。

我写了个代码測试。发现mutex是公平的。condition的測试结果也是差点儿相同。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <pthread.h>
  4. #include <unistd.h>
  5. pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
  6. volatile int mutexCount = 0;
  7. void mutexFairTest(){
  8. int localCount = 0;
  9. while(1){
  10. pthread_mutex_lock(&lock);
  11. __sync_fetch_and_add(&mutexCount, 1);
  12. localCount += 1;
  13. if(mutexCount > 100000000){
  14. break;
  15. }
  16. pthread_mutex_unlock(&lock);
  17. }
  18. pthread_mutex_unlock(&lock);
  19. printf("localCount:%d\n", localCount);
  20. }
  21. int main() {
  22. pthread_mutex_lock(&lock);
  23. pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
  24. pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
  25. pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
  26. pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
  27. pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
  28. pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
  29. pthread_mutex_unlock(&lock);
  30. sleep(100);
  31. }

输出结果是:

[plain] view
plain
copy

  1. localCount:16930422
  2. localCount:16525616
  3. localCount:16850294
  4. localCount:16129844
  5. localCount:17329693
  6. localCount:16234137

还特意在一个单CPU的虚拟机上測试了下。输出的结果差点儿相同。操作系统是ububtu14.04。

參考:

http://en.wikipedia.org/wiki/Spurious_wakeup

http://siwind.iteye.com/blog/1469216

http://www.cppblog.com/Solstice/archive/2013/09/09/203094.html

http://www.cs.cmu.edu/afs/cs/academic/class/15492-f07/www/pthreads.html

4.锁--并行编程之条件变量(posix condition variables)的更多相关文章

  1. c++并发编程之条件变量(Condition Variable)

    条件变量(Condition Variable)的一般用法是:线程 A 等待某个条件并挂起,直到线程 B 设置了这个条件,并通知条件变量,然后线程 A 被唤醒.经典的「生产者-消费者」问题就可以用条件 ...

  2. c++11多线程记录6:条件变量(condition variables)

    https://www.youtube.com/watch?v=13dFggo4t_I视频地址 实例1 考虑这样一个场景:存在一个全局队列deque,线程A向deque中推入数据(写),线程B从deq ...

  3. 第8章 用户模式下的线程同步(4)_条件变量(Condition Variable)

    8.6 条件变量(Condition Variables)——可利用临界区或SRWLock锁来实现 8.6.1 条件变量的使用 (1)条件变量机制就是为了简化 “生产者-消费者”问题而设计的一种线程同 ...

  4. 并行编程条件变量(posix condition variables)

    在整理Java LockSupport.park()东方的,我看到了"Spurious wakeup",通过重新梳理. 首先,可以在<UNIX级别编程环境>在样本: # ...

  5. 多线程编程中条件变量和的spurious wakeup 虚假唤醒

    1. 概述 条件变量(condition variable)是利用共享的变量进行线程之间同步的一种机制.典型的场景包括生产者-消费者模型,线程池实现等. 对条件变量的使用包括两个动作: 1) 线程等待 ...

  6. python多线程编程5: 条件变量同步-乾颐堂

    互斥锁是最简单的线程同步机制,Python提供的Condition对象提供了对复杂线程同步问题的支持.Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还 ...

  7. Linux多线程编程的条件变量

    在stackoverflow上看到一关于多线程条件变量的问题,题主问道:什么时候会用到条件变量,mutex还不够吗?有个叫slowjelj的人做了很好的回答,我再看这个哥们其他话题的一些回答,感觉水平 ...

  8. Linux 多线程编程—使用条件变量实现循环打印

    编写一个程序,开启3个线程,这3个线程的ID分别为A.B.C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示:如:ABCABC….依次递推. 使用条件变量来实现: #inc ...

  9. 039条件变量同步(Condition)

    也是锁,这个锁多加了wait(),notify()唤醒一个进程,notifyall()唤醒全部进程方法,创建的时候默认是Rlock类型的锁,可以设置为lock类型的,默认就ok from random ...

随机推荐

  1. echarts源码中关于 判断平台的有用代码

    function detect(ua) { var os = {}; var browser = {}; // var webkit = ua.match(/Web[kK]it[\/]{0,1}([\ ...

  2. eclipse去除所有调试断点 (转)

    今天调试的时候发现之前加了太多断点,想去除所有断点,才想起来一直都没有使用过这个功能,放狗搜了一下,很快找到,记录一下. 方法一: 在工作界面,点window菜单栏,选中Preperences,在Ge ...

  3. arduino ide的串口权限解决

    刚刚到手一个Arduino uno R3的板子,windows下arduino IDE的串口正常使用,linux下却出现权限问题,解决方案如下:(以下操作使用超级用户) 1.查看设备所在的组ls -l ...

  4. 64位linux 汇编

    c源码:testg.c 1 #include<stdio.h>                2                                   3 #define s ...

  5. Python 数据类型-3

    字典 (dict) Python中唯一的映射的类型(哈希表) 字典对象是可变的,但是字典的键必须使用不可变的对象,一个字典可以使用不同类型的键值 定义字典: In [68]: dic = {} #使用 ...

  6. codevs——1154 能量项链(区间DP)

    2006年NOIP全国联赛提高组  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解    题目描述 Description 在Mars星球上,每个Mars人 ...

  7. 2013年9月29日 iOS 周报

    新闻 Apple Tech Talks 2013 在中国上海的iOS Tech Talks活动将于11月12日展开,活动主要针对iOS 7.活动分为App开放日和游戏开放日,主要内容可查看链接.当你看 ...

  8. iOS博客列表

    国外 iOSDevWeekly NSHipster NSBlog objcio Raywenderlich Bignerdranch NSScreencast 需FQ Pilky.me jeremyw ...

  9. BRDF

    Q radiant energy:   J Φ radiant flux: W dQ/dt E irradiance:  W/m2 dΦ/dA I radiant intersity: W/sr  d ...

  10. BF3 里面的z cull reverse reload

    Bf3 siggraph2011的 分享 http://advances.realtimerendering.com/s2011/White,%20BarreBrisebois-%20Renderin ...