0 引言
在上篇文章C++多线程 task--std::promise和std::future_qls315的博客-CSDN博客
介绍了std::promise和std::future,其可以模拟一次性事件通知机制(这也是条件变量的功能之一)
为了补充相应的事件通知机制相应的方式,本文继续介绍std::condition_variable(条件变量).
1 std::condition_variable(条件变量)
条件变量是一种C++标准提供的同步原语,其可以用来阻塞一个或者多个线程,直到某个线程修改了共享变量(condition),并且向条件变量发送通知。
执行修改共享变量的线程,必须执行如下过程
- 获得std::mutex,可通过std::lock_guard管理
- 修改共享变量
- 执行条件变量的notify_one或者notify_all方法
具体来说,条件变量的工作原理可参考下图
2 条件变量相应接口介绍
一个条件变量cv可以看作一个生产者也可以看作一个消费者,其提供的接口如下所示
cv.notify_one() // 通知一个正在等待的线程
cv.notify_all() // 通知所有正在等待的线程
cv.wait(lock, ...) // 等待一个通知,lock一般为std::unique_lock
cv.wait_for(lock, relTime, ...) // 等待一个通知,如果等待的间隔到relTime时,仍未等到通知,线程被唤醒
// lock为std::unique_lock
cv.wait_until(lock, absTime, ...) // 等待一个通知,如果到absTime时间点,仍未等到通知,线程被唤醒, lock为std::unique_lock
cv.native_handle(). // 返回这个条件变量的底层实现的handle
3 条件变量的注意事项
使用条件变量时一般需要了解如下两个概念:Lost Wakeup 和 Spurious Wakeup。
Lost Wakeup :
所谓丢失唤醒,指的是在接受线程进入等待状态(也即加入wait queue)之前,生产者线程便发布了相应的通知(事件), 此时导致接受者线程陷入一直等待的状态。
Spurious Wakeup:
所谓虚假唤醒,指的是在没有收到相应的通知,线程也会被唤醒。
为了演示Lost wakeup, 可参考如下代码
#include <thread>
#include <iostream>
#include <condition_variable>
#include <mutex>
std::mutex mut;
std::condition_variable cv;
void consume() {
std::cout << "Consume thread: wait notify\n";
std::unique_lock<std::mutex> lck(mut);
cv.wait(lck);
std::cout << "Consume thread: receve notify\n";
}
void produce() {
std::cout << "Produce thread: send notify\n";
cv.notify_one();
}
int main() {
std::thread t1(produce);
std::thread t2(consume);
t1.join();
t2.join();
return 0;
}
上述输出结果如下所示,也即通知事件先发布,后进入wait queue。
4 代码示例
由上述第3部分可知,条件变量的wait接口必须要使用谓词,防止出现lost wakeup。
通过《C++ Concurrency in Action》可知,条件变量的wait接口一般是使用忙等待实现的,并对其进行了部分优化,简言之,其实现如下
template<typename Predicate>
void minimal_wait(std::unique_lock<std::mutex>& lk,Predicate pred){
while(!pred()){
lk.unlock();
lk.lock(); }
}
为了方便理解,可以将条件变量的wait接口按照如下方式进行理解
cv.wait(lck, pred)
==>
std::unique_lock<std::mutex> lck(mut)
while (!pred) {
cv.wait(lck);
}
本部分的代码参考于《C++ Concurrency in Action》 第2版,并做了相应修改
#include <thread>
#include <iostream>
#include <condition_variable>
#include <mutex>
std::mutex mut;
bool data_ready{false};
std::condition_variable cv;
void data_preparation_thread() {
std::cout << "data ready\n";
{
std::lock_guard<std::mutex> lg(mut);
data_ready = true;
}
cv.notify_one();
}
void data_processing_thread() {
std::cout << "data_processing_thread wait notify\n";
std::unique_lock<std::mutex> lck(mut);
cv.wait(lck, []() { return data_ready; });
std::cout << "data_processing_thread: receve notify\n";
}
int main() {
std::thread t1(data_preparation_thread);
std::thread t2(data_processing_thread);
t1.join();
t2.join();
return 0;
}
其输出结果可自行验证。
5 总结
本文总结了现代C++种多线程中使用的条件变量的相关介绍及可能出现的问题。通过本文应该可以初步使用条件变量。