c++ 条件变量的使用,实战

2023-05-16

 

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
//线程通信,结合mutex
//一个线程,多个线程处于等待,通知一个或多个
mutex m;
condition_variable cv;

int main()
{
	thread **th = new thread*[10];//开辟线程的指针数组
	for(int i=0;i<10;i++)
	{
		th[i] = new thread([](int index)
		{
			unique_lock<mutex> lck(m);
			cv.wait_for(lck,chrono::hours(1000));//一直等
			cout<<"index:"<<index<<endl;
		},i);//传递参数
		this_thread::sleep_for(chrono::milliseconds(1000));//错开
	}
	
	for(int i=0;i<10;i++)
	{
		lock_guard<mutex> lckg(m);//解锁向导
		cv.notify_one();//挨个的通知
	}
/*	
	for(int i=0;i<10;i++)
	{
		lock_guard<mutex> lckg(m);//解锁向导
	}
	cv.notify_all();
	*/
	for(int i=0;i<10;i++)
	{
	    th[i]->join();
		delete th[i];
	}
	delete []th;//释放指针数组
   //cout << "Hello World";
   return 0;
}

 C++11条件变量使用详解_Keep Moving~-CSDN博客_c++ 条件变量

condition_variable介绍

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

其主要成员函数如下:
在这里插入图片描述


条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:

 

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

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

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

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

细节说明

在条件变量中只能使用std::unique_lock<std::mutex>说明

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://blog.csdn.net/c_base_jin/article/details/89741247

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++ 条件变量的使用,实战 的相关文章

  • CAN FD 介绍

    随着电动汽车 xff0c 无人驾驶汽车技术的快速发展 xff0c 以及对汽车高级驾驶辅助系统和人机交互HMI需求的增加 xff0c 传统的CAN总线在传输速率和带宽等方面越来越显得力不从心 xff0c 其主要原因如下 xff1a 1 通常整
  • FlexRay 介绍

    汽车上的总线技术包括 xff1a LIN CAN CAN FD FlexRay MOST及Ethernet xff0c 我们之前已经分享了LIN xff0c CAN CAN FD总线 在开始阅读之前 xff0c 如果你对已介绍的总线技术还不
  • FlexRay总线原理及应用

    由于传统的CAN解决方案不能满足汽车线控系统 xff08 X by Wire xff09 的要求 于是在 2000 年 9 月 xff0c 宝马和戴姆勒克莱斯勒联合飞利浦和摩托罗拉成立了 FlexRay 联盟 该联盟致力于推广 FlexRa
  • SENT信号介绍

    Vehicle攻城狮 The people who are crazy enough to think they can change the world are the ones who do SENT背景介绍 提到车载总线 xff0c
  • Linux 日志管理

    常用日志文件 系统日志是由一个名为syslog的服务管理的 xff0c 如以下日志文件都是由syslog日志服务驱动的 xff1a var log boot log xff1a 录了系统在引导过程中发生的事件 xff0c 就是Linux系统
  • SPI 通讯协议

    Cuitbasics 汽车ECU设计 2 2 当您将微控制器连接到传感器 xff0c 显示器或其他模块时 xff0c 您是否考虑过这两种设备是如何相互通信的 xff1f 他们到底在说什么 xff1f 事实上电子设备之间的通信就像人类之间的交
  • UART串口通讯

    UART代表通用异步接收器 发送器也称为串口通讯 xff0c 它不像SPI和I2C这样的通信协议 xff0c 而是微控制器中的物理电路或独立的IC UART的主要目的是发送和接收串行数据 xff0c 其最好的优点是它仅使用两条线在设备之间传
  • 一文搞懂AUTOSAR的DEM模块

    Dem全称为Diagnostic Event Manager xff0c 负责故障事件的处理 故障数据的存储和管理 简单说其功能是故障事件确认前的故障debounce xff0c 故障事件确认时的故障数据存储 xff0c 故障发生后的故障老
  • linux父子进程问题——孤儿进程与僵尸进程[总结]

    今天遇到一个linux进程启动时指定Max open files不对的问题 xff0c 导致程序建立socket异常 xff0c 进而导致fullgc问题 xff0c 影响正常服务 所以顺带又温习了下linux下的父子进程的特性 孤儿进程与
  • C++11/14/17一些好用新特性自己整理下

    1 override xff1a 子类继承父类的时候 xff0c 子类虚函数名字写错了或者参数列表不匹配会变成另外一个函数编译器无法判断对错 xff0c 和你写不写virtual也没关系 xff0c 这时候可以在虚函数结尾加上overrid
  • vector中emplace_back方法的用途

    在写代码的过程中 xff0c CLion提醒我把 span style background color ffd900 push back span 方法替换成 span style background color ffd900 empl
  • constexper+const+常量表达式

    常量表达式 xff08 const expression xff09 是指值不会改变并且在编译过程就能得到计算结果的表达式 显然 xff0c 字面值属于常量表达式 xff0c 用常量表达式初始化的 const 对象也是常量表达式 一个对象
  • 这篇 CPU Cache,估计也没人看

    无论你写什么样的代码都会交给 CPU 来执行 xff0c 所以 xff0c 如果你想写出性能比较高的代码 xff0c 这篇文章中提到的技术还是值得认真学习的 另外 xff0c 千万别觉得这些东西没用 xff0c 这些东西非常有用 xff0c
  • 每天一个 Linux 命令

    https blog csdn net k346k346 category 9267835 html uptime 命令 1 命令简介 uptime 用于显示系统总共运行了多长时间和系统的平均负载 无选项 uptime 命令会显示一行信息
  • Docker 安装Jenkins并配置Maven

    系统环境 系统版本 xff1a Centos7 9 docker安装参考此链接 xff1a https blog csdn net clover661 article details 122226083 下载docker时候如果报错参考 x
  • 一文详解自动驾驶的运行设计域(ODD)| 自动驾驶系列

    一文详解自动驾驶的运行设计域 xff08 ODD xff09 n 自动驾驶系列 2021年4月30日 xff0c SAE发布了第四版J3016 驾驶自动化分级 xff0c 这是即2014年1月16日 2016年9月30日 2018年6月15
  • QNX BSP分析

    QNX相关历史文章 xff1a QNX简介QNX Neutrino微内核QNX IPC机制QNX进程管理器QNX资源管理器QNX字符I OQNX之编写资源管理器 xff08 一 xff09 QNX之编写资源管理器 xff08 二 xff09
  • SOA面向服务的分布式架构详解

    导语 xff1a SOA作为一种面向服务的架构 xff0c 是一种软件架构设计的模型和方法论 从业务角度来看 xff0c 一切以最大化 服务 的价值为 出发点 xff0c SOA利用企业现有的各种软件体系 xff0c 重新整合并构建起一套新
  • 自动驾驶软件架构之:中间件与SOA(一)

    本文是将中间件作为一个专题 xff0c 专门展开进行详细的分析和讨论 中间件相关技术在计算机分布式系统中发展了很多年 xff0c 尤其在互联网服务 大型商业系统中得到广泛使用 随着智能网联汽车的发展 xff0c 现代汽车也逐步增加了以太网支

随机推荐

  • 嵌入式系统BSP基础知识

    嵌入式系统BSP基础知识 板级支持包 BSP 是定义如何支持特定硬件设备 设备组或硬件平台的信息集合 BSP 包括有关设备上存在的硬件功能的信息和内核配置信息以及所需的任何其他硬件驱动程序 除了用于基本和可选平台功能的通用 Linux 软件
  • constexpr

    constexpr 标志返回值或者其他表达式是常量 xff0c 在编译时就会被计算出来 这个关键字常被用来 C 43 43 const 和 constexpr 的区别 xff1f 知乎 include lt iostream gt usin
  • inline namespace

    include lt iostream gt using namespace std namespace ALL namespace V2014 void fun int num cout lt lt 34 int 34 lt lt 34
  • 进程与线程

    对于操作系统来说 xff0c 一个任务就是一个进程 xff08 Process xff09 xff0c 比如打开一个浏览器就是启动一个浏览器进程 xff0c 打开一个记事本就启动了一个记事本进程 xff0c 打开两个记事本就启动了两个记事本
  • 详解SOME/IP协议文档

    以下内容来源于AutoSar官网的AUTOSAR PRS SOMEIPProtocol文档 详解SOME IP协议文档 2 知乎 以下内容来源于AutoSar官网的AUTOSAR PRS SOMEIPProtocol文档 SOME IP P
  • AP AUTOSAR——Update and Configuration Management UCM

    15 Update and Configuration Management 15 1 What is Update and Configuration Management 更新和配置管理是Adaptive Platform Servic
  • 基于Docker安装Jenkins并实现CI/CD实战部署

    本实践介绍了利用Jenkins和docker技术 xff0c 如何实现CI CD的各环节的步骤 xff0c 包括环境准备 xff0c 代码提交 xff0c 编译程序 xff0c 构建镜像 xff0c 部署一套完整的安装部署流程 工具介绍 x
  • 左值引用与右值引用

    include lt iostream gt using namespace std void change int amp rnum 引用就是变量名的别名 rnum 61 111 c 43 43 中能用引用的地方 xff0c 就不要使用指
  • C++ 11的移动语义

    目录 可拷贝和可移动的概念 移动构造函数和移动赋值函数 小结移动构造和移动赋值std move 使用 std move 实现一个高效的 swap 函数Move and swap 技巧参考 可拷贝和可移动的概念 在面向对象中 xff0c 有的
  • UDS-统一诊断服务

    什么是诊断服务 xff1f 在还没有诊断服务的时候 xff0c 如果车辆故障 xff0c 需要有经验的师傅长时间的摸排查找 xff0c 费时费力 而车辆的ECU节点有了诊断模块后 xff0c 就具有了诊断功能 xff0c 这样车辆如果有了故
  • AP AUTOSAR——Network Management

    16 Network Management 16 1 What is Network Management 网络管理是Adaptive Platform Services中的一个功能集群 作为AP AUTOSAR平台的服务 xff0c 网络
  • AP AUTOSAR——Security Management

    11 Security Management 11 1 What is Security Management 安全管理是自适应平台体系结构中的一个功能集群 作为一个功能集群 xff0c 安全管理由多个模块组成 xff0c 这些模块向在Ad
  • 如何制作S32V234的Linux5.x版本BSP

    脚本是编译S32v Linux5 x版本bsp文件的流程 官方也有这个指导说明文档 xff0c 主要是第2 3章内容 xff0c 可以参考着执行 1 下面描述的所有步骤都已在Ubuntu 20 04LTS上 xff08 本机或通过虚拟机 x
  • C++经典面试题100例及答案

    1 面向对象的程序设计思想是什么 答 xff1a 把数据结构和对数据结构进行操作的方法封装形成一个个的对象 2 什么是类 答 xff1a 把一些具有共性的对象归类后形成一个集合 xff0c 也就是所谓的类 3 对象都具有的两方面特征是什么
  • C++面试100题,1——40

    C与c 43 43 有什么不同 xff1f 在c 43 43 中能使用引用就不要使用指针 xff0c 要改变一个一级指针就要用一个二级指针 要改变一个二级指针就要用一个三级指针 xff0c 会变得越来越复杂 A类中的func1是虚函数 xf
  • (TDA4 BSP )Texas Instruments Jacinto 7 J721E (DRA829/TDA4xM) BSP 如何制作?

    1 1 1 Download and Install the SDK Processor SDK Linux for J721e Documentation https software dl ti com jacinto7 esd pro
  • 解决Linux 环境 GLIBCXX_3.4.15‘ not found问题

    升级Centos系统之后 xff0c 运行filezilla时 xff0c 出现如下错误的提示信息 xff1a filezilla usr lib libstdc 43 43 so 6 version 96 GLIBCXX 3 4 15 3
  • 两台Linux服务器之间传输文件的四种方法(转载)

    在日常服务器租用中 xff0c 有时需要将文件从一台服务器传到另一台服务器 xff0c 下面给大家介绍四种linux服务器之间传输文件方式 scp 优点 简单方便 xff0c 安全可靠 xff1b 支持限速参数 缺点 不支持排除目录 用法
  • 任务间通信 | 邮箱、消息队列

    本文分享自中移OneOS公众号 任务间通信 上篇讲解了任务间同步 xff0c 在本篇中主要讲解任务间通信机制 xff0c 并对邮箱及消息队列进行详细介绍 通过对其概念 详细设计 接口设计等的讲解帮助开发者更好的理解其在操作系统中的应用 任务
  • c++ 条件变量的使用,实战

    include lt iostream gt include lt thread gt include lt mutex gt include lt condition variable gt using namespace std 线程通