加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

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

(2025-10-16 11:55:53)
标签:

it

分类: 技术
https://download.csdn.net/blog/column/11315550/125554572

0 引言
在上篇文章C++多线程 task--std::promise和std::future
介绍了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
#include
#include
#include

std::mutex mut;
std::condition_variable cv;

void consume() {
  std::cout << "Consume thread: wait notify\n";
  std::unique_lock 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
void minimal_wait(std::unique_lock& lk,Predicate pred){
    while(!pred()){
        lk.unlock();
        lk.lock(); }
}
为了方便理解,可以将条件变量的wait接口按照如下方式进行理解

cv.wait(lck, pred)

==>

std::unique_lock lck(mut)
while (!pred) {
  cv.wait(lck);
}
本部分的代码参考于《C++ Concurrency in Action》 第2版,并做了相应修改

#include
#include
#include
#include

std::mutex mut;
bool data_ready{false};
std::condition_variable cv;

void data_preparation_thread() {
  std::cout << "data ready\n";
  {
    std::lock_guard lg(mut);
    data_ready = true;
  }
  
  cv.notify_one();
}

void data_processing_thread() {
  std::cout << "data_processing_thread wait notify\n";
  std::unique_lock 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++种多线程中使用的条件变量的相关介绍及可能出现的问题。通过本文应该可以初步使用条件变量。

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有