C++11条件变量使用详解

C++11条件变量使用详解condition variable 介绍在 C 11 中 我们可以使用条件变量 condition variable 实现多个线程间的同步操作 当条件不满足时 相关线程被一直阻塞 直到某种条件出现 这些线程才会被唤醒 其主要成员函数如下 条件变量是利用线程间共享的全局变量进行同步的一种机制 主要包括两个动作 一个线程因等待 条件变量的条件成立 而挂起 另外一个线程使 条件成立 给出信

condition_variable介绍

在C++11中,我们可以使用条件变量(condition_variable)实现多个线程间的同步操作;当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。

  1. 一个线程因等待”条件变量的条件成立”而挂起;
  2. 另外一个线程使”条件成立”,给出信号,从而唤醒被等待的线程。

为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起;通常情况下这个锁是std::mutex,并且管理这个锁 只能是 std::unique_lock

RAII模板类

上面提到的两个步骤,分别是使用以下两个方法实现:

  • 等待条件成立使用的是condition_variable类成员wait 、wait_for 或 wait_until。
  • 给出信号使用的是condition_variable类成员notify_one或者notify_all函数。

细节说明

在条件变量中只能使用std::unique_lock

说明

unique_lock和lock_guard都是管理锁的辅助类工具,都是RAII风格;它们是在定义时获得锁,在析构时释放锁。它们的主要区别在于unique_lock锁机制更加灵活,可以再需要的时候进行lock或者unlock调用,不非得是析构或者构造时。它们的区别可以通过成员函数就可以一目了然。
在这里插入图片描述

wait/wait_for说明

线程的阻塞是通过成员函数wait()/wait_for()/wait_until()函数实现的。这里主要说明前面两个函数:

  • wait()成员函数

    函数声明如下:

    void wait( std::unique_lock<std::mutex>& lock ); //Predicate 谓词函数,可以普通函数或者lambda表达式 template< class Predicate > void wait( std::unique_lock<std::mutex>& lock, Predicate pred ); 

    wait 导致当前线程阻塞直至条件变量被通知,或虚假唤醒发生,可选地循环直至满足某谓词

  • wait_for()成员函数

    函数声明如下:

    1) template< class Rep, class Period > std::cv_status wait_for( std::unique_lock<std::mutex>& lock, const std::chrono::duration<Rep, Period>& rel_time);2) template< class Rep, class Period, class Predicate > bool wait_for( std::unique_lock<std::mutex>& lock, const std::chrono::duration<Rep, Period>& rel_time, Predicate pred); 

    wait_for 导致当前线程阻塞直至条件变量被通知,或虚假唤醒发生,或者超时返回

    返回值说明:

    (1)若经过 rel_time 所指定的关联时限则为 std::cv_status::timeout ,否则为 std::cv_status::no_timeout 。

    (2)若经过 rel_time 时限后谓词 pred 仍求值为 false 则为 false ,否则为 true 。

以上两个类型的wait函数都在会阻塞时,自动释放锁权限,即调用unique_lock的成员函数unlock(),以便其他线程能有机会获得锁。这就是条件变量只能和unique_lock一起使用的原因,否则当前线程一直占有锁,线程被阻塞。

notify_all/notify_one

notify函数声明如下:

void notify_one() noexcept; 若任何线程在 *this 上等待,则调用 notify_one 会解阻塞(唤醒)等待线程之一。 void notify_all() noexcept; 若任何线程在 *this 上等待,则解阻塞(唤醒)全部等待线程。 

虚假唤醒

在正常情况下,wait类型函数返回时要不是因为被唤醒,要不是因为超时才返回,但是在实际中发现,因此操作系统的原因,wait类型在不满足条件时,它也会返回,这就导致了虚假唤醒。因此,我们一般都是使用带有谓词参数的wait函数,因为这种(xxx, Predicate pred )类型的函数等价于:

while (!pred()) //while循环,解决了虚假唤醒的问题 { 
    wait(lock); } 

原因说明如下:

假设系统不存在虚假唤醒的时,代码形式如下:

if (不满足xxx条件) { 
    //没有虚假唤醒,wait函数可以一直等待,直到被唤醒或者超时,没有问题。 //但实际中却存在虚假唤醒,导致假设不成立,wait不会继续等待,跳出if语句, //提前执行其他代码,流程异常 wait(); } //其他代码 ... 

正确的使用方式,使用while语句解决:

while (!(xxx条件) ) { 
    //虚假唤醒发生,由于while循环,再次检查条件是否满足, //否则继续等待,解决虚假唤醒 wait(); } //其他代码 .... 

条件变量使用

在这里,我们使用条件变量,解决生产者-消费者问题,该问题主要描述如下:

生产者-消费者问题,也称有限缓冲问题,是一个多进程/线程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个进程/线程——即所谓的“生产者”和“消费者”,在实际运行时会发生的问题。

生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据

同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者

生产者-消费者代码如下:

std::mutex g_cvMutex; std::condition_variable g_cv; //缓存区 std::deque<int> g_data_deque; //缓存区最大数目 const int MAX_NUM = 30; //数据 int g_next_index = 0; //生产者,消费者线程个数 const int PRODUCER_THREAD_NUM = 3; const int CONSUMER_THREAD_NUM = 3; void producer_thread(int thread_id) { 
    while (true) { 
    std::this_thread::sleep_for(std::chrono::milliseconds(500)); //加锁 std::unique_lock <std::mutex> lk(g_cvMutex); //当队列未满时,继续添加数据 g_cv.wait(lk, [](){ 
    return g_data_deque.size() <= MAX_NUM; }); g_next_index++; g_data_deque.push_back(g_next_index); std::cout << "producer_thread: " << thread_id << " producer data: " << g_next_index; std::cout << " queue size: " << g_data_deque.size() << std::endl; //唤醒其他线程  g_cv.notify_all(); //自动释放锁 } } void consumer_thread(int thread_id) { 
    while (true) { 
    std::this_thread::sleep_for(std::chrono::milliseconds(550)); //加锁 std::unique_lock <std::mutex> lk(g_cvMutex); //检测条件是否达成 g_cv.wait( lk, []{ 
    return !g_data_deque.empty(); }); //互斥操作,消息数据 int data = g_data_deque.front(); g_data_deque.pop_front(); std::cout << "\tconsumer_thread: " << thread_id << " consumer data: "; std::cout << data << " deque size: " << g_data_deque.size() << std::endl; //唤醒其他线程 g_cv.notify_all(); //自动释放锁 } } int main() { 
    std::thread arrRroducerThread[PRODUCER_THREAD_NUM]; std::thread arrConsumerThread[CONSUMER_THREAD_NUM]; for (int i = 0; i < PRODUCER_THREAD_NUM; i++) { 
    arrRroducerThread[i] = std::thread(producer_thread, i); } for (int i = 0; i < CONSUMER_THREAD_NUM; i++) { 
    arrConsumerThread[i] = std::thread(consumer_thread, i); } for (int i = 0; i < PRODUCER_THREAD_NUM; i++) { 
    arrRroducerThread[i].join(); } for (int i = 0; i < CONSUMER_THREAD_NUM; i++) { 
    arrConsumerThread[i].join(); } return 0; } 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/207056.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午2:33
下一篇 2026年3月19日 下午2:33


相关推荐

  • 8个免费和最佳开源视频流服务器软件

    视频流服务器是系统软件,无论用户使用什么操作系统,都可以使用户流视频。某些视频流服务器基于Linux与操作系统无关,可以安装在任何计算机上。很少有人针对特定的平台,例如Windows,Linux或Mac。什么是视频流服务器?远程提供和管理视频媒体内容的服务器软件称为“视频流服务器”。并非所有的视频播放器都可以与视频流服务器一起使用,而是需要一个完美的视频播放器来与远程服务器一起使用来缓冲和…

    2022年4月5日
    101
  • 解决 GPG error

    解决 GPG errorapt keyadvkeyser ubuntu comrecv keysD45DF2E8

    2025年9月26日
    4
  • 单片机C语言入门自学指南(前期准备)

    单片机C语言入门自学指南(前期准备)很多学习单片机的伙伴们刚入手的时候都因为 C 语言卡壳了 也因此放弃了单片机的学习 百度 单片机 C 语言 一大堆的资料 一阵手忙脚乱 不知道如何筛选适合自己学习的资料 也不知道从何下手 为了方便伙伴们尽快的掌握单片机 C 语言知识 今天我就给大家分享一下单片机 C 语言到底如何入门自学 C 语言是一种偏向底层的语言 更多的是应用在嵌入式领域 或者操作系统的开发 单片机只是 C 语言应用的一个小分支 下面 我给大家简单的介绍一下单片机 C 语言入门前期准备 1 在某宝上 买个开发

    2026年3月26日
    2
  • 智能体数据验证:AI Agents for Beginners数据格式检查

    智能体数据验证:AI Agents for Beginners数据格式检查

    2026年3月15日
    1
  • activiti工作流简介[通俗易懂]

    activiti工作流简介[通俗易懂] 工作流简介工作流定义工作流:  就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。工作流管理系统(WorkflowManagementSystem,WfMS):  工作流管理系统是一个软件系统,它完成工作量的定义…

    2022年10月6日
    7
  • python视频识别_视频人员行为识别(Action Recognition)

    python视频识别_视频人员行为识别(Action Recognition)一.提出背景目标:给定一段视频,通过分析,得到里面人员的动作行为。问题:可以定义为一个分类问题,通过对预定的样本进行分类训练,解决一个输入视频的多分类问题。这里提出的问题是简单的图片(视频)分类问题,该问题的前提条件是:场景目标为单人,并且占据图片比较大的比例,如下图所示:还有一类问题是基于行人检测,去估计行人的姿态和动作,暂时不在本篇讨论范围内。二.行为识别的发展和其他领域一样,我们还是先从…

    2022年6月3日
    36

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号