条件变量signal与unlock的顺序
编写同步队列时,有用到条件变量,对操作队列的线程进行同步。当队列为空时,允许get线程挂起,直到add线程向队列添加元素并通过唤醒条件变量,get线程继续向下运行。条件变量在多线程程序中用来实现“等待->唤醒”逻辑常用的方法。条件变量要和互斥量相联结,以避免出现条件竞争:一个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件。使用条件变量进行同步时,通常以如下方式进行编码:
pthread_mutex_t mutex;
pthread_cond_t cond; //Get线程
int Get()
{
pthread_mutex_lock(&mutex);
while (queue.empty())
{
pthread_cond_wait(&cond, &mutex);
}
item = queue.front();
pthread_mutex_unlock(&mutex);
return item;
}
//Add线程
void Add(int item)
{
pthread_mutex_lock(&mutex);
queue.push(item);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
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
条件变量signal与unlock的顺序的更多相关文章
- 条件变量用例--解锁与signal的顺序问题
我们知道,当调用signal/broadcast唤醒等待条件变量的其他线程时,既可以在加锁的情况下调用signal/broadcast,也可以在解锁的情况下调用. 那么,到底哪种情况更好呢?man手册 ...
- 深入浅出 Java Concurrency (9): 锁机制 part 4 锁释放与条件变量 (Lock.unlock And Condition)
本小节介绍锁释放Lock.unlock(). Release/TryRelease unlock操作实际上就调用了AQS的release操作,释放持有的锁. public final boolean ...
- linux多线程同步pthread_cond_XXX条件变量的理解
在linux多线程编程中,线程的执行顺序是不可预知的,但是有时候由于某些需求,需要多个线程在启动时按照一定的顺序执行,虽然可以使用一些比较简陋的做法,例如:如果有3个线程 ABC,要求执行顺序是A-- ...
- 转: 【Java并发编程】之二十:并发新特性—Lock锁和条件变量(含代码)
简单使用Lock锁 Java5中引入了新的锁机制--Java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作.Lock接 ...
- 深入解析条件变量(condition variables)
深入解析条件变量 什么是条件变量(condition variables) 引用APUE中的一句话: Condition variables are another synchronization m ...
- 学习pthreads,使用条件变量进行多线程之间的同步
条件变量提供另一种多线程同步的方法.互斥量通过控制对共享数据的访问来同步任务.条件变量可以根据数据的值来同步任务.条件变量是当一个事件发生时发送信号的信号量.一旦事件发生,可能会有多个线程在等待信号, ...
- 线程同步,条件变量pthread_cond_wait
与互斥锁不同,条件变量是用来等待而不是用来上锁的.条件变量用来自动阻塞一个线程,直到某特殊情况发生为止.条件变量使我们可以睡眠等待某种条件出现.条件变量是利用线程间共享的全局变量进行同步的一种机制,主 ...
- 【Java并发编程】:并发新特性—Lock锁和条件变量
简单使用Lock锁 Java5中引入了新的锁机制——Java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作.Lock接 ...
- 多线程编程中条件变量和的spurious wakeup 虚假唤醒
1. 概述 条件变量(condition variable)是利用共享的变量进行线程之间同步的一种机制.典型的场景包括生产者-消费者模型,线程池实现等. 对条件变量的使用包括两个动作: 1) 线程等待 ...
随机推荐
- BI—脚不一样的感觉
在这个网络智能的时代,假设生活和智能挂不上边那就太落后啦!尤其IT行业更是如此,前不久还在用微软的office做报表,这几天就吵吵着换成BI,那么BI是什么?有什么用?怎么用?等等带着这一系列的问题来 ...
- Sublime Text Package Collections
JavaScriptNext - ES6 Syntax packagecontrol.io github.com Better JavaScript language definition for T ...
- c# webbrowser 获取用户选中文字
原文:c# webbrowser 获取用户选中文字 最近一直被一个问题困扰,有一个文本框,一个webbrowser控件,一个上下文菜单, webbrowser 获取用户选中文字" tit ...
- jQuery性能优化篇
jQuery高级技巧——性能优化篇 阅读目录 通过CDN(Content Delivery Network)引入jQuery库 减少DOM操作 适当使用原生JS 选择器优化 缓存jQuery对象 定义 ...
- Ubuntu 中查看内核版本和系统版本的三个命令
一.查看内核版本:cat /proc/version 二.查看内核版本:uname -a 三.查看系统版本:lsb_release -a 四.查看发行版类型:cat /etc/issue
- 发布一个参考ssdb,使用go类似的实现redis高性能nosql:ledisdb
起因 ledisdb是一个參考ssdb.採用go实现,底层基于leveldb,相似redis的高性能nosql数据库,提供了kv,list,hash以及zset数据结构的支持. 我们如今的应用极大的依 ...
- MVC中的其他新特性
MVC中的其他新特性 (GlobalImport全局导入功能) 默认新建立的MVC程序中,在Views目录下,新增加了一个_GlobalImport.cshtml文件和_ViewStart.cshtm ...
- [转]HttpClient使用详解
Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们再讨论),它不仅是客户端发送Http请求变得容易,而且 ...
- Redis实现高并发分布式序列号
使用Redis实现高并发分布式序列号生成服务 序列号的构成 为建立良好的数据治理方案,作数据掌握.分析.统计.商业智能等用途,业务数据的编码制定通常都会遵循一定的规则,一般来讲,都会有自己的编码规则和 ...
- C# 跨线程呼叫控制
在C# 应用程序开发, 我们经常要UI作线程分开,防止界面停止响应. 同一时候我们又须要在工作线程中更新UI界面上的控件, 以下介绍几种经常使用的方法 阅读文件夹 线程间操作无效 第一种办法:禁止编 ...