C++ 条件变量的使用
绪论
并发编程纷繁复杂,其中用于线程同步的主要工具——条件变量,虽然精悍,但是要想正确灵活的运用却并不容易。
对于条件变量的理解有三个难点:
- 为什么
wait
函数需要将解锁和阻塞、唤醒和上锁这两对操作编程原子的? - 为什么
wait
函数需要配合while
进行使用? - 通知线程是应该先
notify
再unlock
还是先unlock
后notify
?
希望大家看完下面的介绍能够得到想要的答案。想要了解更多关于C++并发编程信息可以移步的我仓库:C++并发编程
条件变量
C++提供了两种条件变量的实现:std::condition_variable
和std::condition_variable_any
。前者只能和std::mutex
配合使用,后者只需要符合互斥的标准即可。因为std::condition_variable_any
更通用,所以可能产生额外的开销,如果没什么特殊需要,尽可能使用std::condition_variable
条件变量是非常重要的线程同步的手段(目前我认为是最重要的),因此对其的深入理解至关重要。
条件变量总是和互斥一起配合使用,互斥用于保护共享数据,条件变量用于
- 通知(通知线程)
- 判断该共享数据是否满足条件(等待线程)
通知线程往往先通过互斥保护共享数据,对数据进行一定的修改后再发送通知(
notify_one()、notify_all()
)。需要注意的是我们应尽可能在临界区内发送通知,从而避免可能出现的优先级翻转和条件变量失效问题。虽然临界区外通知可以让等待线程一旦被唤醒就能立即解锁互斥查看是否满足情况,但是在Pthread进行wait morphint后基本上两者没有性能上的差距。详细的分析可以参考博客:条件变量用例–解锁与signal的顺序问题。notify_one()
理论上只会唤醒一个等待线程,适用于共享变量数量发生变化的情况,例如通知消息队列中的消息个数增加。notify_all()
会唤醒所有等待该条件变量的线程,适用于共享变量状态发生变化的情况,例如通知所有工作线程开始计算。
等待线程先获得互斥,然后将锁和判定条件传递给
wait
函数等待返回。wait
函数首先会根据判断条件判断是否满足条件(返回true
)如果满足条件,则直接返回(互斥依旧上锁)
如果不满足条件,则阻塞等待,并解锁互斥(让其他线程得以修改共享数据的状态)。直到被
notify
函数唤醒,再次上锁,判断条件是否满足。这里的阻塞和解锁、唤醒和上锁都是原子的,就是为了避免两个动作分别执行出现的条件竞态。- 解锁和阻塞是原子的:lock → !pred() → unlock → sleep;如果变量的改变以及唤醒事件发生在unlock和sleep中间,那么你不会检测到,也就是错过了这次唤醒。假如下次唤醒依赖于此次唤醒的成功(也就是说不会主动唤醒第二次),那么将发生死锁。
- 唤醒和上锁是原子的:wakeup → lock → !pred :如果条件在wakeup和lock之间从满足变成了不满足(不是因为其他等待线程修改,而是因为负责唤醒的线程自己再次修改了条件),那么此次唤醒将失败。假如后面条件的再次满足依赖于此次条件满足成功(也就是说条件不会再主动满足),那么将发生死锁。
需要理解的是上面的死锁的出现是有限定条件的(例如唤醒之间的依赖、条件满足的依赖),虽然大多数情况下没有这么严格的条件,但是工具本身需要避免这种危险的情况。
原子操作保证了重要的唤醒和条件满足都能够至少被一个等待线程看到。
可以看到
wait
函数内部需要解锁互斥,所以就不能使用不提供unlock
函数的lock_guard
,而应该使用和互斥有相同接口的unique_lock
。
其实C++的线程库是对pthread库的封装,因此也可以像pthread库一样只传入互斥,解锁并等待通知,一旦接收到通知后再上锁,然后在一个
while
循环中进行判断。while (!pred()) {cond_.wait(lk); //调用pthread_cond_wait }
对于传入判定条件的版本,其实内部也是这样的一个封装罢了。
之所以说
notify_one()
理论上只会唤醒一个等待线程是因为存在调用一次notify_one()
却唤醒了多个线程的可能性,甚至有时候没有调用notify
等待线程都被唤醒,称这种意外唤醒等待线程的情况为伪唤醒。按照C++标准的规定,这种伪唤醒出现的数量和频率都不确定,因此要求等待线程的判定函数不能有副作用(可重用),并且需要在唤醒后再次判断条件是否满足,如果不满足则需要重新等待。这也是为什么上面的代码使用while
进行条件判断而不是if
的原因。
消息队列
//
// Created by edward on 22-11-16.
// use condtion_variable to genenrate a thread safe message queue
//#include "utils.h"
#include <mutex>
#include <queue>
#include <condition_variable>
#include <iostream>
#include <thread>
#include <string>template<typename T>
class MessageQueue {public:void push(T t) {std::lock_guard lk(mtx_); //互斥保护数据queue_.push(std::move(t));cond_.notify_one(); //临界区内发送通知,避免优先级反转和条件变量失效}T pop() {T frnt;std::unique_lock lk(mtx_);cond_.wait(lk, [&](){return !queue_.empty();});frnt = std::move(queue_.front());queue_.pop();return frnt;}
private:mutable std::mutex mtx_;mutable std::condition_variable cond_;std::queue<T> queue_;
};using namespace std;template<typename T>
void data_prepare(MessageQueue<T> &messageQueue) {T t;while (cin >> t) {messageQueue.push(std::move(t));}
}template<typename T>
void data_process(MessageQueue<T> &messageQueue) {T t;int idx = 0;while (true) {t = messageQueue.pop(); //数据的处理在临界区外edward::print("[", idx++, "]:", t);}
}int main() {MessageQueue<string> messageQueue;edward::print("test begin:");thread preparer(data_prepare<string>, ref(messageQueue));thread processer(data_process<string>, ref(messageQueue));preparer.join();//不用等待processer,如果preparer结束,则直接推出进程return 0;
}
运行结果
其中用到了我自己写的库函数头文件utils
,如果想要了解更多信息可以移步C++ 工具函数库
C++ 条件变量的使用相关推荐
- 信号量,互斥锁,条件变量的联系与区别
转自:http://blog.chinaunix.net/u3/108685/showart_2127853.html 信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程, ...
- linux互斥锁和条件变量,如何理解互斥锁和条件变量?
下面的代码出自<Unix/Linux编程实践教程>,作用是用两个线程分别统计两个文件的单词的数目,并在主线程中计算总数.下面是运行截图: 但是看了半天还是难以理解下面代码中的加锁.解锁以及 ...
- 【C++】多线程互斥锁、条件变量
我们了解互斥量和条件变量之前,我们先来看一下为什么要有互斥量和条件变量这两个东西,了解为什么有这两东西之后,理解起来后面的东西就简单很多了!!! 先来看下面这段简单的代码: int g_num = 0 ...
- Linux多线程同步------条件变量
先来看下<Linux高性能服务器编程>中对条件变量的描述: 上述话可以总结为: 多线程中某一个线程依赖于另外一个线程对共享数据的改变时,就可以使用条件变量! 用消费者生产者的来理解条件变量 ...
- C++ 多线程:条件变量 std::condition_variable
文章目录 描述 使用 描述 头文件<condition_variable> 定义 class condition_variable; 简介 之前我们也已经介绍过了C++多线程中互斥变量存在 ...
- 【C++】多线程与条件变量【三】
文章目录 1 条件变量是什么? 实例1: 2 条件变量本质? 3 引入条件变量的原因? 实例2: 实例3: 实例4: 4 如何使用条件变量? 4.1 std::condition_variable 实 ...
- Linux下多线程编程互斥锁和条件变量的简单使用
Linux下的多线程遵循POSIX线程接口,称为pthread.编写Linux下的多线程程序,需要使用头文件pthread.h,链接时需要使用库libpthread.a.线程是进程的一个实体,是CPU ...
- C++条件变量使用详解
1. condition_variable介绍 在C++11中,我们可以使用条件变量(condition_variable)实现多个线程间的同步操作:当条件不满足时,相关线程被一直阻塞,直到某种条件出 ...
- 条件变量 pthread_cond_wait
1.先了解一下等待队列.(默认大家了解mutex,如果不了解:https://blog.csdn.net/qq_33890670/article/details/79967231) 等待队列,是指li ...
- 信号量 互斥量 条件变量
原文:https://blog.csdn.net/qq_32646795/article/details/78221005 本文打算写一些和锁有关的东西,谈一谈我对锁的原理和实现的理解,主要包含以下方 ...
最新文章
- Android 通过http协议数据交互
- 郑州升达学院计算机考试,第35次全国计算机等级考试报名工作通知
- Oracle客户端安装教程
- MySQL max_allowed_packet设置及问题
- 一文搞懂一致性hash的原理和实现
- 杂论架构—架构是一种设计 转。。。
- pytest allure测试报告_用Pytest+Allure生成漂亮的HTML图形化测试报告
- opencv 多边形近似物体形状
- dlib android 识别时间,android dlib调用
- 第三周作业之效能分析
- 图算法在反欺诈中的应用
- 华为eNSP静态路由原理与配置实例详解
- 【腾讯TMQ】TBS主线众测实践之路
- 安装和使用openBMC官网维护的qemu
- centos6 安装 directAdmin
- JAVA RESTful WebService实战笔记(二)
- 16 hue框架使用
- 高效团队建设与管理 学习心得
- 用javaScript制作爱心特效
- 豆豆趣事[2013年06月]