4.锁--并行编程之条件变量(posix condition variables)
在整理Java LockSupport.park()的东东。看到了个"Spurious wakeup"。又一次梳理下。
首先来个《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);
- }
一个简单的消息生产者和消费者的代码。它们之间用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函数。
- pthread_cond_wait(mutex, cond):
- value = cond->value; /* 1 */
- pthread_mutex_unlock(mutex); /* 2 */
- pthread_mutex_lock(cond->mutex); /* 10 */
- if (value == cond->value) { /* 11 */
- me->next_cond = cond->waiter;
- cond->waiter = me;
- pthread_mutex_unlock(cond->mutex);
- unable_to_run(me);
- } else
- pthread_mutex_unlock(cond->mutex); /* 12 */
- pthread_mutex_lock(mutex); /* 13 */
- pthread_cond_signal(cond):
- pthread_mutex_lock(cond->mutex); /* 3 */
- cond->value++; /* 4 */
- if (cond->waiter) { /* 5 */
- sleeper = cond->waiter; /* 6 */
- cond->waiter = sleeper->next_cond; /* 7 */
- able_to_run(sleeper); /* 8 */
- }
- 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。
- void enqueue_msg(struct msg *mp) {
- pthread_mutex_lock(&qlock);
- mp->m_next = workq;
- workq = mp;
- <strong> pthread_mutex_unlock(&qlock);
- pthread_cond_signal(&qready);</strong>
- }
有的地方给出的是先调用pthread_cond_signal。再调用pthread_mutex_unlock:
- void enqueue_msg(struct msg *mp) {
- pthread_mutex_lock(&qlock);
- mp->m_next = workq;
- workq = mp;
- pthread_cond_signal(&qready);
- pthread_mutex_unlock(&qlock);
- }
先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循环来推断条件是否成立?
- while (workq == NULL)
- pthread_cond_wait(&qready, &qlock);
而不用if来推断?
- if (workq == NULL)
- 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的測试结果也是差点儿相同。
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <unistd.h>
- pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
- volatile int mutexCount = 0;
- void mutexFairTest(){
- int localCount = 0;
- while(1){
- pthread_mutex_lock(&lock);
- __sync_fetch_and_add(&mutexCount, 1);
- localCount += 1;
- if(mutexCount > 100000000){
- break;
- }
- pthread_mutex_unlock(&lock);
- }
- pthread_mutex_unlock(&lock);
- printf("localCount:%d\n", localCount);
- }
- int main() {
- pthread_mutex_lock(&lock);
- pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
- pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
- pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
- pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
- pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
- pthread_create(new pthread_t, NULL, (void * (*)(void *))&mutexFairTest, NULL);
- pthread_mutex_unlock(&lock);
- sleep(100);
- }
输出结果是:
- localCount:16930422
- localCount:16525616
- localCount:16850294
- localCount:16129844
- localCount:17329693
- 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)的更多相关文章
- c++并发编程之条件变量(Condition Variable)
条件变量(Condition Variable)的一般用法是:线程 A 等待某个条件并挂起,直到线程 B 设置了这个条件,并通知条件变量,然后线程 A 被唤醒.经典的「生产者-消费者」问题就可以用条件 ...
- c++11多线程记录6:条件变量(condition variables)
https://www.youtube.com/watch?v=13dFggo4t_I视频地址 实例1 考虑这样一个场景:存在一个全局队列deque,线程A向deque中推入数据(写),线程B从deq ...
- 第8章 用户模式下的线程同步(4)_条件变量(Condition Variable)
8.6 条件变量(Condition Variables)——可利用临界区或SRWLock锁来实现 8.6.1 条件变量的使用 (1)条件变量机制就是为了简化 “生产者-消费者”问题而设计的一种线程同 ...
- 并行编程条件变量(posix condition variables)
在整理Java LockSupport.park()东方的,我看到了"Spurious wakeup",通过重新梳理. 首先,可以在<UNIX级别编程环境>在样本: # ...
- 多线程编程中条件变量和的spurious wakeup 虚假唤醒
1. 概述 条件变量(condition variable)是利用共享的变量进行线程之间同步的一种机制.典型的场景包括生产者-消费者模型,线程池实现等. 对条件变量的使用包括两个动作: 1) 线程等待 ...
- python多线程编程5: 条件变量同步-乾颐堂
互斥锁是最简单的线程同步机制,Python提供的Condition对象提供了对复杂线程同步问题的支持.Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还 ...
- Linux多线程编程的条件变量
在stackoverflow上看到一关于多线程条件变量的问题,题主问道:什么时候会用到条件变量,mutex还不够吗?有个叫slowjelj的人做了很好的回答,我再看这个哥们其他话题的一些回答,感觉水平 ...
- Linux 多线程编程—使用条件变量实现循环打印
编写一个程序,开启3个线程,这3个线程的ID分别为A.B.C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示:如:ABCABC….依次递推. 使用条件变量来实现: #inc ...
- 039条件变量同步(Condition)
也是锁,这个锁多加了wait(),notify()唤醒一个进程,notifyall()唤醒全部进程方法,创建的时候默认是Rlock类型的锁,可以设置为lock类型的,默认就ok from random ...
随机推荐
- 《Linux命令行与shell脚本编程大全 第3版》Linux命令行---15
以下为阅读<Linux命令行与shell脚本编程大全 第3版>的读书笔记,为了方便记录,特地与书的内容保持同步,特意做成一节一次随笔,特记录如下:
- sublime text3 无法安装插件
下载 Package Control.sublime-package 点击这里下载: 打开sublime3 -> 首选项 -> 浏览插件 (程序自动打开插件目录) 删除 Package C ...
- 常用的Linux系统命令
一.linux简介 ...
- WEB接口测试之Jmeter接口测试自动化 (一)(初次接触)
软件测试自动化从不同的测试阶段分类,可从下层到上层依次分为单元测试-->接口测试-->界面自动化测试. 单元测试一般有开发人员自行完成,而界面自动化测试合适的测试条件又很难达到,测试人员在 ...
- GLB串
题目描述 只要一个字符串中包含大写“GLB”,高老板就认为这是一个GLB串.现在给你一些字符串,请你帮高老板判断这些字符串是不是GLB串. 输入 首先是一个整数T,表示T行数据,每行一个字符串(只包括 ...
- POJ 2411 Mondriaan's Dream [经典状态压缩dp]
题意:略. 思路:这一题开始做的时候完全没有思路,便去看了别人的题解. 首先,对于这个题目解法想有一个初步的了解,请看这里:http://www.2cto.com/kf/201208/146894.h ...
- python的列表元组字典集合比较
定义 方法 列表 可以包含不同类型的对象,可以增减元素,可以跟其他的列表结合或者把一个列表拆分,用[]来定义的 eg:aList=[123,'abc',4.56,['inner','list'],7- ...
- Mac 安装brew和安装composer
一.安装brew /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/mast ...
- jquery dataTable 获取某行数据
DataTable API table.row(rowSelector [,modifier]) 注table是dataTable的对象 该方法有两个默认参数 第一个是选择器 第二个是可选的 请注意, ...
- [转] 使用SVN进行源码管理
原文地址:gyzhao's, 使用SVN进行源码管理(下) 软件下载 1. Viusal SVN, Download(官网),安装该软件之前,请先安装TortoiseSVN,Download. 2. ...