本节来了解下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. intellij debug模式提示 Method breakpoints may dramatically slow down debugging 解决办法

    直接上图........ 点击图中按钮  或者 快捷键(Ctrl - Shift -F8 ) 出现下图  

  2. 动态能力理论&知识管理理论--商业之所见

    动态能力理论:企业整合,建立和再配置内外部资源以适应快速变化环境的能力. (1)"动态"指的是适应不断变化的环境,企业必须具有不断更新自身能力的能力: (2)"能力&qu ...

  3. C语言中标准输出的缓冲机制

    什么是缓冲区 缓存区是内存空间的一部分,再内存中,内存空间会预留一定的存储空间,这些存储空间是用来缓冲输入和输出的数据,预留的这部分空间就叫做缓冲区. 其中缓冲区还会根据对应的是输入设备还是输出设备分 ...

  4. Grafana导入 json 文件的 dashboard 错误 Templating Failed to upgrade legacy queries Datasource xxx not found

    前言 编辑或者修改后的 dashboard 保存为 json 文件,在其他环境导入使用,报错 Failed to upgrade legacy queries Datasource xxxxxxx w ...

  5. go 逐行读取文件

    前言 文件 I/O,特别是对文件的读写是编程语言中重要的功能.通常,我们需要逐行读取文件. GO 提供了 bufio 软件包,实现了有缓冲的 I/O.它包装一个 io.Reader 或 io.Writ ...

  6. php 配置Gmail 发送邮件 PHPMailer

    hotmail 获取邮箱授权码 准备 首先你应该登陆https://mail.google.com地址,注册一个Gmail邮箱,然后设置开启IMAP访问 打开设置,开启IMAP访问 获取应用专用密码 ...

  7. HTTP/1.1、HTTP/2、HTTP/3

    HTTP/1.1 相比 HTTP/1.0 性能上的改进: 使用长连接的方式改善了 HTTP/1.0 短连接造成的性能开销. 支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来, ...

  8. 使用键盘控制gazebo小车模型运动

    博客地址:https://www.cnblogs.com/zylyehuo/ gazebo小车模型创建详见另一篇博客 博客地址:gazebo小车模型(附带仿真环境) - zylyehuo - 博客园 ...

  9. openssl基础使用(密码学 linux)

    目录        实验原理        实验过程            一.对称加密                1.使用rc4加解密                2.使用AES加解密     ...

  10. 使用df命令

    1.使用df命令,查看整体的磁盘使用情况 df命令是用来查看硬盘的挂载点,以及对应的硬盘容量信息.包括硬盘的总大小,已经使用的大小,剩余大小.以及使用的空间占有的百分比等. 最常用的命令格式就是: 1 ...