C++多线程std::condition_variable(条件变量)

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++种多线程中使用的条件变量的相关介绍及可能出现的问题。通过本文应该可以初步使用条件变量。

参考:std::condition_variable - cppreference.com

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qls315

感觉好可打赏几毛钱增强更新动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值