博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++11: std::condition_variable
阅读量:5768 次
发布时间:2019-06-18

本文共 6810 字,大约阅读时间需要 22 分钟。

hot3.png

需要特别注意的是: std::condition_variable最好配合std::unique_lock使用!!!!!!!!

有时候,被不同线程执行的task必须彼此等待。所以对于“并发操作”来说同步化除了防止data race之外还有其他原因。

你可能会争辩说我们已经引入了Future允许我们停下来直到另外一个线程结束提供数据,但是这样等待一个线程结束才能提供数据要经历一个函数的过程呀!浪费了很多时间,而且Future对象只能get()一次结果。

Condition Variable(条件变量)的意图:

bool ready_flag;std::mutex mutex;//等待直到ready_flag被设置为true;{   std::unique_lock
unique_l(mutex); while(!ready_flag){ unique_l.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); unique_l.lock(); }}

但是上面的轮询等待条件的具备通常不是最好的做法(虽然是合理的也没有问题的)。

等待中的线程消耗宝贵的CPU时间重复检验ready_flag,且当它锁住mutex时候“负责设置ready_flag为true"的那个线程会被阻塞(blocking)...而且上面的代码让我们很难找到适当的sleep周期。

#include 
#include
#include
#include
#include
std::mutex mutex;bool ready_flag = false;std::condition_variable ready_condition;void thread_one(){ std::cout << "
" << std::endl; std::cin.get(); { std::lock_guard
lock_g(mutex); ready_flag = true; } ready_condition.notify_one(); //注意这里.}void thread_two(){ { std::unique_lock
unique_l(mutex); ready_condition.wait(unique_l, []()->bool { return ready_flag; }); } std::cout << "done(完成)" << std::endl;}int main(){ std::future
result_one = std::async(std::launch::async, thread_one); std::future
result_two = std::async(std::launch::async, thread_two); return 0;}

在包含必要的头文件之后,我们还需要三个条件以保证在线程之间通讯.

1,一个用以表示条件真的满足了的flag(也就是此处的ready_flag).

2,一个mutex(也就是上面代码中的std::mutex mutex);

3,一个condition variable(也就是上面代码中的ready_condition);

再来一个例子:

#include 
#include
#include
#include
#include
#include
std::condition_variable ready_condition;std::mutex mutex;std::queue
queue;void provider(const int& val){ std::cout << "test1" << std::endl; //把不同的值放到std::queue中去. for (int i = 0; i < 6; ++i) { { std::lock_guard
lock_g(mutex); //这里确保了queue的读写是atomic(不可分割的). queue.push(val + i); } ready_condition.notify_one(); //每次quque中加入新元素都会唤醒一个线程输出该元素. //还有注意上面这条并不在保护区内,也不需要在保护区内.虽然std::queue是的读写操作不能同时进行 //但是当保护区结束的时候push()操作已经完成了.而对std::queue执行读操作在另外线程的时候也是被上锁的因此不可能会产生data race. std::this_thread::sleep_for(std::chrono::milliseconds(val));//休眠一段时间保证consumer运行.不然notify完了没有wait的什么事也不会发生. //因为我们的consumer()在main()函数中是稍微落后一点provider()的. }}void consumer(const int& number){ std::cout << "test2" << std::endl; while (true) { int val; { std::unique_lock
unique_l(mutex); ready_condition.wait(unique_l, []()->bool { return !queue.empty(); }); val = queue.front(); queue.pop(); } std::cout << "consumer " << number << ": " << val << std::endl; }}int main(){ std::future
result_one = std::async(std::launch::async, provider, 100); std::future
result_two = std::async(std::launch::async, provider, 200); std::future
result_three = std::async(std::launch::async, provider, 300); std::future
result_four = std::async(std::launch::async, consumer, 1); std::future
result_five = std::async(std::launch::async, consumer, 2); return 0;}

 

class std::condition_variable

构造函数:

condition_variable();	condition_variable (const condition_variable&) = delete;

构造一个std::condition_variable对象,该对象不能被copy,也不能被移动.

成员函数部分:

std::condition_variable::wait

这里需要指出的是wait()和notify_one()或者notify_all()必须成对使用.

void wait (unique_lock
& lck); template
void wait (unique_lock
& lck, Predicate pred);

该函数在解锁他所管理的mutex的情况下阻塞(block)正在调用wait()的线程(此时正处于阻塞状态)直到其他线程中的notify_one()或者notify_all()被调用,再次lock他管理的Mutex.

需要注意的是,std::condition_variable的wait()总是在已经被上锁的mutex的基础上执行操作的.wait()会指向下面三个操作暂时解锁当前线程已经锁住的mutex:

1,解锁(unlock)mutex然后使当前线程进入等待状态.

2,收到notify信号后解除等待状态.

3,再次锁住mutex,由前面std::unique_lock来决定何时解.

还有一点需要注意的是:wait系列的函数都可以再接受一个callable object,用以判断条件是否满足,同时防止处于等待状态的线程假醒.

比如在上面的第一个例子中:

ready_condition.wait(unique_l, []()->bool { return ready_flag; });

其实相当于:

{  std::unique_lock
unique(mutex); while(!ready_flag){ unique.unlock(); ready_condition.wait(unique_l); unique.lock();}

 

我们再来看一个更加详细的例子:

#include 
#include
#include
#include
std::condition_variable cv;std::mutex cv_m;int i = 0;bool done = false; void waits(){ std::unique_lock
lk(cv_m); std::cout << "Waiting... \n"; cv.wait(lk, []{return i == 1;}); std::cout << "...finished waiting. i == 1\n"; done = true;} void signals(){ std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "Notifying falsely...\n"; cv.notify_one(); // waiting thread is notified with i == 0. // cv.wait wakes up, checks i, and goes back to waiting std::unique_lock
lk(cv_m); i = 1; while (!done) { std::cout << "Notifying true change...\n"; lk.unlock(); cv.notify_one(); // waiting thread is notified with i == 1, cv.wait returns std::this_thread::sleep_for(std::chrono::seconds(1)); lk.lock(); }} int main(){ std::thread t1(waits), t2(signals); t1.join(); t2.join();}

可能Output结果:

Waiting... Notifying falsely...Notifying true change......finished waiting. i == 1

 

std::condition_variable::wait_for

template 
cv_status wait_for (unique_lock
& lck, const chrono::duration
& rel_time); template
bool wait_for (unique_lock
& lck, const chrono::duration
& rel_time, Predicate pred);

该函数在解锁(unlock)当前线程已经上锁(lock)mutex的情况下阻塞(block)当前线程一段时间(duration),直到其他线程中的notify_one()或者notify_all()被调用,或者超过了这段时间也没有收到来自其他线程的notify_one()(或者是notify_all())那么该函数都会返回一个std::cv_status(稍后会介绍)类型的参数.

请注意:std::condition_variable::wait_for也是可以接受一个callable object.其效果更wait()接受一个callable object类似只不过是在给定的时间段内.最好这么做能够防止假醒.

std::condition_variable::wait_until

template 
cv_status wait_until (unique_lock
& lck, const chrono::time_point
& abs_time);template
bool wait_until (unique_lock
& lck, const chrono::time_point
& abs_time, Predicate pred);

该函数在解锁(unlock)当前线程已经上锁(lock)了的mutex的情况下阻塞(block)当前线程到某个时间点。如果其他线程中的notify_one()(或者是notify_all())在该时间点之前到达之前被调用了那么返回一个std::cv_status类型的对象,如果超过了该指定的时间点也没有收到任何来自notify_one()或者notify_all()的信号也返回一个std::cv_status类型的对象.

std::condition_variable::notify_one

void notify_one() noexcept;

唤醒(weak)某个正在等待该信号的线程,如果有多个线程都在等待该信号的话具体是哪个会被唤醒(weak)是没法指定的.如果没有任何线程等待该型号那么什么事情也不做.

std::condition_variable_notify_all

void notify_all() noexcept;

唤醒(weak)所有的正在等待信号的线程,如果没有等待者什么都不做.

std::cv_status

enum class cv_status;
cv_status::timeout //超时的话返回这个.
cv_status::no_timeout //没有超时.

 

转载于:https://my.oschina.net/SHIHUAMarryMe/blog/679163

你可能感兴趣的文章
[转]MVC4项目中验证用户登录一个特性就搞定
查看>>
用Perl编写Apache模块续二 - SVN动态鉴权实现SVNAuth 禅道版
查看>>
Android 阴影,圆形的Button
查看>>
C++概述
查看>>
卡特兰数
查看>>
006_mac osx 应用跨屏幕
查看>>
nginx中配置文件的讲解
查看>>
MindNode使用
查看>>
SQL Server 2016 Alwayson新增功能
查看>>
HTTP库Axios
查看>>
CentOS7下安装python-pip
查看>>
认知计算 Cognitive Computing
查看>>
左手坐标系和右手坐标系 ZZ
查看>>
陀螺仪主要性能指标
查看>>
Java 架构师眼中的 HTTP 协议
查看>>
Linux 目录结构和常用命令
查看>>
Linux内存管理之mmap详解 (可用于android底层内存调试)
查看>>
利润表(年末)未分配利润公式备份
查看>>
Android开发中ViewStub的应用方法
查看>>
gen already exists but is not a source folder. Convert to a source folder or rename it 的解决办法...
查看>>