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的用条件变量同步线程的例子.故此来和大家一起分 ...
随机推荐
- Servlet的cookie使用,500报错,tomcat和cookie语法不兼容解决
出现类似上图的错误,应该是tomcat和cookie的语法不兼容 cookie不要用逗号","作分隔符,换井号#试试就可以了
- adoquery怎样判断数据在缓存中有修改啊
ADOQry.Filtered:=false; ADOQry.Filtered:=true; ADOQry.FilterGroup:=fgPendingRecords ;Fil ...
- 在delphi中生成GUID
什么是 GUID ? 全球唯一标识符 (GUID) 是一个字母数字标识符,用于指示产品的唯一性安装.在许多流行软件应用程序(例如 Web 浏览器和媒体播放器)中,都使用 GUID. GUID 的格式为 ...
- springboot+jpa+mysql+swagger整合
Springboot+jpa+MySQL+swagger整合 创建一个springboot web项目 <dependencies> <dependency> < ...
- JUC同步锁(五)
根据锁的添加到Java中的时间,Java中的锁,可以分为"同步锁"和"JUC包中的锁". 一.同步锁--synchronized关键字 通过synchroniz ...
- BizTalk Server 如何处理大消息
什么是大消息? 遗憾的是,此问题的答案不而直接与特定的消息大小,绑定,取决于你的 Microsoft 的特定瓶颈 BizTalk Server 系统. 与大消息关联的问题可分为以下几类: 内存不足错误 ...
- Nginx 用分片提示缓存效率
L:107 slice 模块 Syntax:slice size;Default: slice 0; Context:http, server, location 功能:通过range协议将大文件分解 ...
- Linux下tomcat中多项目配置druid报错的问题
这里有多种方法,推荐修改tomcat配置,即在启动JVM配置中设置如下: -Ddruid.registerToSysProperty=true 详解参见该博: https://blog.csdn.ne ...
- VSCode里面HTML添加CSS时没有提示
看到知乎上的回答,vscode修改设置的: "editor.parameterHints": true, "editor.quickSuggestions": ...
- LeetCode:152_Maximum Product Subarray | 最大乘积连续子数组 | Medium
题目:Maximum Product Subarray Find the contiguous subarray within an array (containing at least one nu ...