C++11并发——多线程条件变量std::condition_variable(四)
https://www.jianshu.com/p/a31d4fb5594f
https://blog.csdn.net/y396397735/article/details/81272752
https://www.cnblogs.com/haippy/p/3252041.html
std::condition_variable 是条件变量,
当 std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex) 来锁住当前线程。
当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程。
std::condition_variable 对象通常使用 std::unique_lock<std::mutex> 来等待,如果需要使用另外的 lockable 类型,可以使用 std::condition_variable_any 类,本文后面会讲到 std::condition_variable_any 的用法。
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable std::mutex mtx; // 全局互斥锁.
std::condition_variable cv; // 全局条件变量.
bool ready = false; // 全局标志位. void do_print_id(int id)
{
std::unique_lock <std::mutex> lck(mtx);
while (!ready) // 如果标志位不为 true, 则等待...
cv.wait(lck); // 当前线程被阻塞, 当全局标志位变为 true 之后,
// 线程被唤醒, 继续往下执行打印线程编号id.
std::cout << "thread " << id << '\n';
} void go()
{
std::unique_lock <std::mutex> lck(mtx);
ready = true; // 设置全局标志位为 true.
cv.notify_all(); // 唤醒所有线程.
} int main()
{
std::thread threads[];
// spawn 10 threads:
for (int i = ; i < ; ++i)
threads[i] = std::thread(do_print_id, i); std::cout << "10 threads ready to race...\n";
go(); // go! for (auto & th:threads)
th.join(); return ;
}
concurrency ) ./ConditionVariable-basic1
threads ready to race...
thread
thread
thread
thread
thread
thread
thread
thread
thread
thread
好了,对条件变量有了一个基本的了解之后,我们来看看 std::condition_variable 的各个成员函数。
std::condition_variable 构造函数
default (1) |
condition_variable(); |
---|---|
copy [deleted] (2) |
condition_variable (const condition_variable&) = delete; |
std::condition_variable 的拷贝构造函数被禁用,只提供了默认构造函数。
std::condition_variable::wait() 介绍
unconditional (1) |
void wait (unique_lock<mutex>& lck); |
---|---|
predicate (2) |
template <class Predicate> |
std::condition_variable 提供了两种 wait() 函数。当前线程调用 wait() 后将被阻塞(此时当前线程应该获得了锁(mutex),不妨设获得锁 lck),直到另外某个线程调用 notify_* 唤醒了当前线程。
在线程被阻塞时,该函数会自动调用 lck.unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。
另外,一旦当前线程获得通知(notified,通常是另外某个线程调用 notify_* 唤醒了当前线程),wait() 函数也是自动调用 lck.lock(),使得 lck 的状态和 wait 函数被调用时相同。
在第二种情况下(即设置了 Predicate),只有当 pred 条件为 false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred 为 true 时才会被解除阻塞。因此第二种情况类似以下代码:
while (!pred()) wait(lck);
#include <iostream> // std::cout
#include <thread> // std::thread, std::this_thread::yield
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable std::mutex mtx;
std::condition_variable cv; int cargo = ;
bool shipment_available()
{
return cargo != ;
} // 消费者线程.
void consume(int n)
{
for (int i = ; i < n; ++i) {
std::unique_lock <std::mutex> lck(mtx);
cv.wait(lck, shipment_available);
std::cout << cargo << '\n';
cargo = ;
}
} int main()
{
std::thread consumer_thread(consume, ); // 消费者线程. // 主线程为生产者线程, 生产 10 个物品.
for (int i = ; i < ; ++i) {
while (shipment_available())
std::this_thread::yield();
/*
std::this_thread::yield: 当前线程放弃执行,操作系统调度另一线程继续执行。
即当前线程将未使用完的“CPU时间片”让给其他线程使用,
等其他线程使用完后再与其他线程一起竞争"CPU"。
std::this_thread::sleep_for: 表示当前线程休眠一段时间,
休眠期间不与其他线程竞争CPU,根据线程需求,等待若干时间。 */
std::unique_lock <std::mutex> lck(mtx);
cargo = i + ;
cv.notify_one();
} consumer_thread.join(); return ;
}
1. std::condition_variable
条件变量提供了两类操作:wait和notify。这两类操作构成了多线程同步的基础。
1.1 wait
wait是线程的等待动作,直到其它线程将其唤醒后,才会继续往下执行。下面通过伪代码来说明其用法:
std::mutex mutex; std::condition_variable cv;
// 条件变量与临界区有关,用来获取和释放一个锁,因此通常会和mutex联用。
std::unique_lock lock(mutex);
// 此处会释放lock,然后在cv上等待,直到其它线程通过cv.notify_xxx来唤醒当前线程,
//cv被唤醒后会再次对lock进行上锁,然后wait函数才会返回。
// wait返回后可以安全的使用mutex保护的临界区内的数据。此时mutex仍为上锁状态 cv.wait(lock)
1.2 notify
了解了wait,notify就简单多了:唤醒wait在该条件变量上的线程。notify有两个版本:notify_one和notify_all。
- notify_one 唤醒等待的一个线程,注意只唤醒一个。
- notify_all 唤醒所有等待的线程。使用该函数时应避免出现惊群效应。
其使用方式见下例:
std::mutex mutex;
std::condition_variable cv;
std::unique_lock lock(mutex);
// 所有等待在cv变量上的线程都会被唤醒。但直到lock释放了mutex,被唤醒的线程才会从wait返回。
cv.notify_all(lock)
// conditionVariable.cpp #include <iostream>
#include <condition_variable>
#include <mutex>
#include <thread> std::mutex mutex_;
std::condition_variable condVar; void doTheWork(){
std::cout << "Processing shared data." << std::endl;
} void waitingForWork(){
std::cout << "Worker: Waiting for work." << std::endl; std::unique_lock<std::mutex> lck(mutex_);
condVar.wait(lck);
doTheWork();
std::cout << "Work done." << std::endl;
} void setDataReady(){
std::cout << "Sender: Data is ready." << std::endl;
condVar.notify_one();
} int main(){ std::cout << std::endl; std::thread t1(waitingForWork);
std::thread t2(setDataReady); t1.join();
t2.join(); std::cout << std::endl; }
该程序有两个子线程: t1和t2。 它们在第33行和第34行中获得可调用的有效负载(函数或函子) waitingForWork和setDataReady。
函数setDataReady通过使用条件变量condVar调用condVar.notify_one()进行通知。 在持有锁的同时,线程T2正在等待其通知: condVar.wait(lck).
虚假的唤醒
细节决定成败。事实上,可能发生的是,接收方在发送方发出通知之前完成了任务。 这怎么可能呢?接收方对虚假的唤醒很敏感。所以即使没有通知发生,接收方也有可能会醒来。
为了保护它,我不得不向等待方法添加一个判断。 这就是我在下一个例子中所做的:
// conditionVariableFixed.cpp #include <iostream>
#include <condition_variable>
#include <mutex>
#include <thread> std::mutex mutex_;
std::condition_variable condVar; bool dataReady; void doTheWork(){
std::cout << "Processing shared data." << std::endl;
} void waitingForWork(){
std::cout << "Worker: Waiting for work." << std::endl; std::unique_lock<std::mutex> lck(mutex_);
condVar.wait(lck,[]{return dataReady;});
doTheWork();
std::cout << "Work done." << std::endl;
} void setDataReady(){
std::lock_guard<std::mutex> lck(mutex_);
dataReady=true;
std::cout << "Sender: Data is ready." << std::endl;
condVar.notify_one();
} int main(){ std::cout << std::endl; std::thread t1(waitingForWork);
std::thread t2(setDataReady); t1.join();
t2.join(); std::cout << std::endl; }
与第一个示例的关键区别是在第11行中使用了一个布尔变量dataReady 作为附加条件。 dataReady在第28行中被设置为true。
它在函数waitingForWork中被检查:
condVar.wait(lck,[]{return dataReady;})
这就是为什么wait方法有一个额外的重载,它接受一个判定。判定是个callable,它返回true或false。
在此示例中,callable是lambda函数。因此,条件变量检查两个条件:判定是否为真,通知是否发生。
关于dataReady
dataReady是个共享变量,将会被改变。所以我不得不用锁来保护它。
因为线程T1只设置和释放锁一次,所以std::lock_guard已经够用了。但是线程t2就不行了,wait方法将持续锁定和解锁互斥体。所以我需要更强大的锁:std::unique_lock。
但这还不是全部,条件变量有很多挑战,它们必须用锁来保护,并且易受虚假唤醒的影响。
大多数用例都很容易用tasks来解决,后续再说task问题。
唤醒不了
条件变量的异常行为还是有的。大约每10次执行一次conditionVariable.cpp就会发生一些奇怪的现象:
我不知道怎么回事,这种现象完全违背了我对条件变量的直觉。
在安东尼·威廉姆斯的支持下,我解开了谜团。
问题在于,如果发送方在接收方进入等待状态之前发送通知,则通知会丢失。C ++标准同时也将条件变量描述为同步机制,“condition_variable类是一个同步原语,可以用来同时阻塞一个线程或多个线程。。。”。
因此,通知消息已经丢失了,但是接收方还在等啊和等啊等啊等啊…
怎么解决这个问题呢?去除掉wait第二个参数的判定可以有效帮助唤醒。实际上,在判定设置为真的情况下,接收器也能够独立于发送者的通知进而继续其工作。
C++11并发——多线程条件变量std::condition_variable(四)的更多相关文章
- Windows:C++11并发编程-条件变量(condition_variable)详解
<condition_variable >头文件主要包含了与条件变量相关的类和函数.相关的类包括 std::condition_variable和 std::condition_varia ...
- 八、条件变量std::condition_variable、wait()、notify_one()、notify_all(粗略)
一.std::condition_variable 用在多线程中. 线程A:等待一个条件满足 线程B:专门在消息队列中扔消息,线程B触发了这个条件,A就满足条件了,可以继续执行 std::condit ...
- c++11 线程间同步---利用std::condition_variable实现
1.前言 很多时候,我们在写程序的时候,多多少少会遇到下面种需求 一个产品的大致部分流程,由工厂生产,然后放入仓库,最后由销售员提单卖出去这样. 在实际中,仓库的容量的有限的,也就是说,工厂不能一直生 ...
- C++并发编程 条件变量 condition_variable,线程安全队列示例
1. 背景 c++11中提供了对线程与条件变量的更好支持,对于写多线程程序方便了很多. 再看c++并发编程,记一下学习笔记. 2. c++11 提供的相关api 3.1 wait wait用于无条件等 ...
- C++11并发——多线程std::thread (一)
https://www.cnblogs.com/haippy/p/3284540.html 与 C++11 多线程相关的头文件 C++11 新标准中引入了四个头文件来支持多线程编程,他们分别是< ...
- C++11并行编程-条件变量(condition_variable)详细说明
<condition_variable >头文件主要包含有类和函数相关的条件变量. 包括相关类 std::condition_variable和 std::condition_variab ...
- Linux 多线程条件变量同步
条件变量是线程同步的另一种方式,实际上,条件变量是信号量的底层实现,这也就意味着,使用条件变量可以拥有更大的自由度,同时也就需要更加小心的进行同步操作.条件变量使用的条件本身是需要使用互斥量进行保护的 ...
- posix多线程--条件变量
条件变量是用来通知共享数据状态信息的. 1.条件变量初始化两种方式:(1)静态初始化pthread_cond_t cond = PTHREAD_COND_INITIALIZER;代码示例如下: #in ...
- Linux Qt使用POSIX多线程条件变量、互斥锁(量)
今天团建,但是文章也要写.酒要喝好,文要写美,方为我辈程序员的全才之路.嘎嘎 之前一直在看POSIX的多线程编程,上个周末结合自己的理解,写了一个基于Qt的用条件变量同步线程的例子.故此来和大家一起分 ...
随机推荐
- re正则表达式-1
匹配/查找/替换/分割函数: import re re.match('aa','aabbcc') 返回对象中span为开始位置和结束位置 re.match('aa','bbaacc') #返回值为No ...
- Visual Studio2012调试时无法命中断点
今天在调试代码的时候发现在Debug模式下无法命中断点,然后一步步去检查原因,最后发现是在项目-->属性-->生成-->高级-->调试信息被设置为None,然后在选项中将其选择 ...
- linux服务器运维
1. grep正则匹配 grep -E "([0-9]{1,3}\.){4}" filepath egrep "([0-9]{1,3}\.){4}" fil ...
- SpringBoot之整合Mybatis范例
依赖包: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http:/ ...
- 51nod 1636
1636 教育改革 我看过题解了还下了数据,表示很惭愧不想说什么,但还是说两句吧 sol: 因为差值很小只有100,所以对数组下标存的是(选择的数值和左端点的差值) f[i][j][k]即为第i天选了 ...
- Nginx 减少关闭连接的time_wait端口数量
L:129
- Centos安装python3
安装环境 系统:阿里云服务器centos7.5系统 看见好多博客对centos安装python3的方式各不相同且都不完整,今天我来完整的演示安装python3 1.下载python3源码包 命令 wg ...
- Bootstrap modal 模态框垂直居中显示补丁
<script> $.fn.modal.Constructor.prototype.adjustDialog1 = function(){ var modalIsOverflowing = ...
- 网页调起QQ聊天
将QQ账号换成正常的QQ号即可,要确保这个QQ支持临时会话 <a href="http://wpa.qq.com/msgrd?v=3&uin=QQ账号&site=qq& ...
- BZOJ 3261 最大异或和(算竞进阶习题)
可持久化Trie 需要知道一个异或的特点,和前缀和差不多 a[p] xor a[p+1] xor....xor a[n] xor x = a[p-1] xor a[n] xor x 所以我们把a[1. ...