本节来了解下C++11 中关于条件变量(condition_variable) 的相关知识,这一部分的内容相信网上已经有了很多的分享,这里仅是对该部分内容学习的记录、总结。

条件变量(condition_variable)

条件变量是一种多线程的同步机制,它能够阻塞线程,直到某一条件满足。条件变量要与互斥量联合使用,以避免出现竞争的情况,当调用condition_variable的一个等待函数时,它使用一个unique_lock对象来锁定线程。

PS:为什么有了lock_guard,还需要unique_lock?

虽然lock_guard挺好用的,但是有个很大的缺陷,在定义lock_guard的地方会调用构造函数加锁,在离开定义域的话lock_guard就会被销毁,调用析构函数解锁。这就产生了一个问题,如果这个定义域范围很大的话,那么锁的粒度就很大,很大程序上会影响效率。

条件变量作用

条件变量的作用是用于多线程之间关于共享数据状态变化的同学。当一个动作需要另外一个动作完成时才能进行,即:当一个线程的行为依赖于另外一个线程对共享数据状态的改变时,这时候就可以使用条件变量。

条件变量是与互斥相关联的一种用于多线程之间关于共享数据状态改变的通信机制。 它将解锁与挂起封装成原子操作。等待一个条件变量时,会解开与该条件变量相关的锁,因此,使用条件变量等待的前提之一就是保证互斥量加锁。该互斥量会被自动加锁,所以,在完成操作之后需要解锁。

底层实现

class condition_variable {
public:
typedef _Cnd_t native_handle_type; condition_variable() { // 构造函数,初始化条件变量,所有的条件变量必须初始化后才能使用。
_Cnd_initX(&_Cnd);
} ~condition_variable() _NOEXCEPT { // 析构函数
_Cnd_destroy(&_Cnd);
} condition_variable(const condition_variable&) = delete;
condition_variable& operator=(const condition_variable&) = delete; void notify_one() _NOEXCEPT { // 唤醒一个在等待线程
_Cnd_signalX(&_Cnd);
} void notify_all() _NOEXCEPT { // 唤醒所有在等待的线程
_Cnd_broadcastX(&_Cnd);
} void wait(unique_lock<mutex>& _Lck) { // 等待
_Cnd_waitX(&_Cnd, &_Lck.mutex()->_Mtx);
} template<class _Predicate>
void wait(unique_lock<mutex>& _Lck, _Predicate _Pred) { // 等待,带有描述式
while (!_Pred())
wait(_Lck);
} template<class _Rep, class _Period>
_Cv_status wait_for(unique_lock<mutex>& _Lck, const chrono::duration<_Rep, _Period>& _Rel_time) {
stdext::threads::xtime _Tgt = _To_xtime(_Rel_time);
return (wait_until(_Lck, &_Tgt));
} template<class _Rep, class _Period, class _Predicate>
bool wait_for(unique_lock<mutex>& _Lck, const chrono::duration<_Rep, _Period>& _Rel_time, _Predicate _Pred) {
stdext::threads::xtime _Tgt = _To_xtime(_Rel_time);
return (wait_until(_Lck, &_Tgt, _Pred));
} template<class _Clock, class _Duration>
_Cv_status wait_until( unique_lock<mutex>& _Lck, const chrono::time_point<_Clock, _Duration>& _Abs_time) {
typename chrono::time_point<_Clock, _Duration>::duration
_Rel_time = _Abs_time - _Clock::now();
return (wait_for(_Lck, _Rel_time));
} template<class _Clock, class _Duration, class _Predicate>
bool wait_until(unique_lock<mutex>& _Lck, const chrono::time_point<_Clock, _Duration>& _Abs_time, _Predicate _Pred) {
typename chrono::time_point<_Clock, _Duration>::duration
_Rel_time = _Abs_time - _Clock::now();
return (wait_for(_Lck, _Rel_time, _Pred));
} _Cv_status wait_until( unique_lock<mutex>& _Lck, const xtime *_Abs_time) {
if (!_Mtx_current_owns(&_Lck.mutex()->_Mtx))
_Throw_Cpp_error(_OPERATION_NOT_PERMITTED);
int _Res = _Cnd_timedwaitX(&_Cnd, &_Lck.mutex()->_Mtx, _Abs_time);
return (_Res == _Thrd_timedout ? cv_status::timeout : cv_status::no_timeout);
} template<class _Predicate>
bool wait_until(unique_lock<mutex>& _Lck, const xtime *_Abs_time, _Predicate _Pred) {
bool _Res = true;
while (_Res && !_Pred())
_Res = wait_until(_Lck, _Abs_time)
!= cv_status::timeout;
return (_Pred());
} native_handle_type native_handle() { // 返回条件变量的句柄
return (_Cnd);
} void _Register(unique_lock<mutex>& _Lck, int *_Ready) {
_Cnd_register_at_thread_exit(&_Cnd, &_Lck.release()->_Mtx, _Ready);
} void _Unregister(mutex& _Mtx) {
_Cnd_unregister_at_thread_exit(&_Mtx._Mtx);
} private:
_Cnd_t _Cnd;
};

在类condition_variable中,包含了私有成员变量、构造函数、析构函数、等待和唤醒等方法。

等待操作: wait()、wait_for()、wait_until()

唤醒操作: notify_one()、notify_all()

条件变量的使用

#include <iostream>                 // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable using namespace std; mutex mtx; // 互斥量
condition_variable cv; // 条件变量
bool ready = false; // 标志量 void print_id(int id) {
unique_lock<mutex> lck(mtx); // 上锁
while (!ready) {
cv.wait(lck); // 线程等待直到被唤醒(释放锁 + 等待,唤醒,在函数返回之前重新上锁)
}
cout << "thread " << id << '\n';
} void go() {
unique_lock<mutex> lck(mtx); // 上锁
ready = true;
cv.notify_all(); // 唤醒所有正在等待(挂起)的线程(在这里面要释放锁,为了在wait函数返回之前能成功的重新上锁)
} int main() {
thread threads[10];
for (int i = 0; i<10; ++i) {
threads[i] = thread(print_id, i);
} cout << "10 threads ready to race...\n";
go(); for (auto& th : threads) {
th.join();
} return 0;
}

来分析一下多线程代码时如何运行的:

1、线程A一来,就将互斥量上锁(持有了锁),ready为false,那么线程A将调用条件变量的wait()方法;

2、在wait()方法中,做的第一件事就是将互斥量解锁(释放持有权),并进入等待状态(在wait()中阻塞,线程A挂起);

3、现在线程B来了,互斥量是没有上锁的,所以线程B能持有锁,同理,接下来线程B也会挂起;

4、当所有线程都挂起了(就绪),此时互斥量也没有被上锁,在主线程中将ready置为true,并调用notify_all()将所有挂起的线程都唤醒;

5、此时所有线程将从wait()方法中返回,比如线程C先返回,在return之前,wait()方法做的最后一件事就是自动将互斥量上锁(线程C重新持有锁,以配合unique_lock的析构函数);

6、由于while循环,此时再判断到ready为true,那么线程C将执行打印id的语句,由于此时只有线程C持有锁,不存在线程竞争问题,执行完打印之后,线程C就结束了,此时由unique_lock的析构函数解锁,释放所有权。

7、由于在wait()方法return之前,会自动重新去持有锁,若此时锁由线程C持有,则其他线程将继续阻塞,直到线程C释放锁;若线程C执行完毕后释放了锁,那么其他线程将会争取锁的持有权,争取到锁的就会像之前的线程C一样;没有争取到的就继续阻塞;

8、以此类推,由于每个线程都join,那么当所有线程执行完毕后,主线程才会继续执行;

实际上,条件变量的wait()、wait_for()、wait_until()方法中所作的事是:解锁 + 等待、唤醒、加锁,这三个是有序发生的。

GCC源码位置:http://ftp.gnu.org/gnu/gcc/

参考:《探索C++多线程》:condition_variable源码(一)

C++ condition_variable 条件变量的更多相关文章

  1. C++11并发——多线程条件变量std::condition_variable(四)

    https://www.jianshu.com/p/a31d4fb5594f https://blog.csdn.net/y396397735/article/details/81272752 htt ...

  2. C++11并行编程-条件变量(condition_variable)详细说明

    <condition_variable >头文件主要包含有类和函数相关的条件变量. 包括相关类 std::condition_variable和 std::condition_variab ...

  3. C++并发编程 条件变量 condition_variable,线程安全队列示例

    1. 背景 c++11中提供了对线程与条件变量的更好支持,对于写多线程程序方便了很多. 再看c++并发编程,记一下学习笔记. 2. c++11 提供的相关api 3.1 wait wait用于无条件等 ...

  4. Windows:C++11并发编程-条件变量(condition_variable)详解

    <condition_variable >头文件主要包含了与条件变量相关的类和函数.相关的类包括 std::condition_variable和 std::condition_varia ...

  5. 八、条件变量std::condition_variable、wait()、notify_one()、notify_all(粗略)

    一.std::condition_variable 用在多线程中. 线程A:等待一个条件满足 线程B:专门在消息队列中扔消息,线程B触发了这个条件,A就满足条件了,可以继续执行 std::condit ...

  6. 条件变量 condition_variable wait_until

    wait_until(阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点) #include <iostream> #include <atomic> #include & ...

  7. 条件变量 condition_variable wait_for

    wait_for(阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后) #include <iostream> #include <atomic> #include < ...

  8. 条件变量 condition_variable wait

    wait(阻塞当前线程,直到条件变量被唤醒) #include <iostream> #include <string> #include <thread> #in ...

  9. C++11 mutex unique_lock condition_variable 互斥锁 条件变量

    创建项目再进行测试比较麻烦,可以使用这个在线编译器进行验证,快速方便 C++11在线编译器 mutex是互斥锁,互斥量 condition_variable是条件变量 std::mutex m; vo ...

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

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

随机推荐

  1. FreeCAD导入立创EDA下载的元件step文件档无法删除PCB部分

    1.问题描述 在使用freeCAD导入step文件的时候,一开始会导入成一个成体,想隐藏某些部件,却只能隐藏整个装配体,就是图示位置无法展开,无法删除部件. 2.解决方法 找到 编辑==>首选项 ...

  2. 使用JAVA8 filter对List多条件筛选

    记录项目开发的过程中遇到的一些问题及解决方法,由于公司操作数据库都是统一使用工具生成的存在一些多表查询模糊查询,这些操作只能在集合方面下手了,比如发送邮件记录方面查询,对用户的名字及邮件模糊检索 年龄 ...

  3. 用Logseq记日报和管理文献

    优缺点浅评 Logseq是一款双链笔记软件,其优点结合使用场景概括来说包括 开箱即用的极简界面,非常适合用来写日报 灵活的双链,强大的PDF标注,适合构建文献库 使用markdown格式来本地存储笔记 ...

  4. Linux - vi & vim 编辑器

    vim 具有程序编辑的能力,可以主动的以字体颜色辨别语法的正确性,方便程序设计. vim 是从vi发展出来的一个文本编辑器.代码补全.编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用. 使 ...

  5. Archlinux 更新失败之驱动与 Xorg 配置错误

    Archlinux系统更新是滚动更新,所以更新失败又被叫做"滚挂了" 此次滚挂发生在1月27日,过了那么久了才想起来该记录了-- 现象 滚挂的现象是,能够进系统,但是笔记本电脑自带 ...

  6. Scala查看源码

    package com.wyh.day01 /** * 1.代码格式化的快捷键 ctrl+alt+L\ * 2.scala查看源代码的快捷键 ctrl+b */ object ScalaLookSou ...

  7. Win10打开IE自动跳转至Edge解决办法

    WIN + R输入inetcpl.cpl弹出Internet属性对话窗口 点击上面菜单中的[高级]选项 滑动右侧滚动条,找到[浏览]项下面的[启用第三方浏览器拓展*]并取消勾选 双击IE浏览器图标测试 ...

  8. Feedalyze - 让你听得见、听得清用户的反馈

    满足用户需求,解决用户问题,获得适当报酬是商业成功最为重要的因素.然而扪心自问,当您推出新产品后,您真的在听.听得见.听得清用户的反馈么? 当今信息传播迅猛,渠道繁多,优秀产品随口碑效应供不应求,劣质 ...

  9. deepseek:如何用php写微信公众号订阅回复事件

    以下是使用 PHP 重写的微信公众号订阅事件回复的示例代码.这个代码实现了用户订阅(关注)公众号时,自动回复一条欢迎消息. PHP 实现代码 <?php // 微信公众平台的Token defi ...

  10. python二级 计算生态

    生态地址: https://pypi.python.org/pypi 常用函数: