C++条件变量详解(一看就懂)

首先,我们先来认识一下条件变量。

条件变量是一种同步原语,通常用于在多线程编程中,使一个线程在特定条件满足之前等待,同时允许其他线程在该条件发生更改时通知等待的线程。
1. “等待”:当条件不满足时(例如,某个资源还未准备好),线程可以选择等待。调用条件变量的 wait 方法会将线程置于阻塞状态,并释放已获取的互斥锁,让其他线程有机会修改条件。
2. “通知”:当条件发生变化时(例如,资源已经准备好),线程可以通过条件变量来通知其他等待这个条件的线程。这可以通过 notify_one(唤醒一个等待线程)或 notify_all(唤醒所有等待线程)方法实现。
3. “重检”:当被通知并从 wait 返回时,线程应重新检查条件以确定其是否真正满足。这是因为存在所谓的"虚假唤醒",即线程可能在条件实际满足之前被唤醒。

‌‌‌‌  例如,我们可以使用条件变量来同步一个生产者线程和一个消费者线程。生产者线程负责生成数据并将其放入缓冲区,消费者线程则从缓冲区中取出数据进行处理。用条件变量可以使消费者线程在缓冲区为空(即数据不足)时等待,而在生产者线程向缓冲区添加数据后,消费者线程会被唤醒并开始处理新数据。

‌‌‌‌  条件变量通常与互斥锁一起使用以确保线程在检查条件和决定等待之间不会被打断,即这两个操作是原子的。同时,在修改条件(比如修改共享数据)时,一般也会使用互斥锁来保证数据一致性。

  1. 条件变量的要点
    1. 检查所等待的条件:在调用 wait 方法之前,我们通常会在一个循环中检查所等待的条件。循环的目的是防止虚假唤醒,即 wait 由于未知原因返回但条件并未满足。
    2. 使用正确的锁:在调用 waitnotify_onenotify_all 时,我们需要使用一个 unique_lock 来保护条件变量。在调用 wait 时,锁会被释放,使其他线程有机会修改条件。等 wait 返回时,锁会被重新获得。
    3. 避免死锁:在使用条件变量时,我们需要留意不要引入死锁。例如,如果两个线程都在等待对方发送信号,但它们都无法发送信号(例如,因为它们都在等待对方释放某个资源),那么就会发生死锁。
    4. 避免滞后唤醒:如果 signal 先于 wait 发生,则该信号会丢失。为了防止这种情况,我们可以在修改条件的同时调用 notify_onenotify_all。这样,如果有其他线程正在等待,它们将立即被唤醒。如果没有线程正在等待,那么这个通知将被忽视。
    5. 注意 notify_allnotify_one 的区别:notify_all 会唤醒所有等待的线程,而 notify_one 只唤醒一个。如果多个线程都在等待同一个条件,那么 notify_all 可能更合适。

这里我们就以消费者和生产者为例,进行代码上的深入讲解。

对于一般的生产者-消费者模型,生产者会产生数据供消费者使用。生产者需要在准备好数据的时候通知消费者,消费者通过判断是否已经有数据来决定要不要消费数据。

生产者和消费者的实现如下所示:


#include<bits/stdc++.h>
#include<mutex>
#include<unistd.h>
using namespace std;
vector<int> buffer;
mutex mtx;
bool dataIsReady = false;
void producer(){while (true) {sleep(2);std::unique_lock<std::mutex> lk(mtx);//随机生成10个数字for (int i = 0; i < 10; i++) {buffer.push_back(rand()%20);}dataIsReady = true;}
}
void consumer(){while (true) {sleep(2);std::unique_lock<std::mutex> lk(mtx);if (!dataIsReady) continue;//输出10个数字之后清空数组for (int i = 0; i < buffer.size(); i++) {if (i) putchar(' ');cout << buffer[i];}cout << endl;buffer.clear();dataIsReady = false;}
}
int main(){srand(time(NULL));thread produc(producer);thread consum(consumer);produc.join();consum.join();return 0;
}

std::unique_lock<std::mutex> lk(mtx); 的作用是创建一个互斥锁的锁对象 lk,并在创建时自动锁定 mtx。这意味着在 lk 的作用域内,互斥锁 mtx 被锁定,确保同一时刻只有一个线程可以访问被保护的资源(如 buffer)。

有两个好处:

  1. 自动解锁:

    std::unique_lock 是一个 RAII(Resource Acquisition Is Initialization)类,意味着它会在作用域结束时自动释放锁。当 lk 超出其作用域时(如函数结束或块结束),lk 的析构函数会被调用,从而自动解锁 mtx。

  2. 避免手动解锁:

    这种设计避免了手动解锁可能带来的错误,比如忘记解锁或在解锁后再次访问共享资源。使用 std::unique_lock 可以使代码更加安全和易于维护。

这种写法可以正常工作,但是存在一个问题:每次consumer线程都会循环判断数据是否准备好,这个过程中线程不会让出资源,因此循环判断会带来不必要的开销。正因如此,才需要学习条件变量。


// condition_variable::notify_all
#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variablestd::mutex mtx;
std::condition_variable cv;
bool ready = false;void print_id (int id) {std::unique_lock<std::mutex> lck(mtx);while (!ready) cv.wait(lck);// ...std::cout << "thread " << id << '\n';
}void go() {//修改ready标记,并通知打印线程工作std::unique_lock<std::mutex> lck(mtx);ready = true;cv.notify_all();
}int main (){std::thread threads[10];// 创建10个线程,每个线程当ready标记为真时打印自己的id号for (int i=0; i<10; ++i)threads[i] = std::thread(print_id,i);std::cout << "10 threads ready to race...\n";go();                       // go!for (auto& th : threads) th.join();return 0;
}

父女水果问题

问题描述:
  父亲、母亲分别向一个果盘中放置一个水果。父亲放置苹果,母亲放置橘子。儿子专门等待果盘中的苹果。女儿专门等待果盘中的橘子。当果盘中准备好水果以后,儿子和女儿分别根据自己的需要拿走水果。
  这其实也是一个生产者和消费者的模型,使用条件变量实现如下:


#include<bits/stdc++.h>
#include<mutex>
#include<unistd.h>
using namespace std;
mutex plate_mtx;                //盘子互斥量
condition_variable plate_cv;    //条件通知
//三个条件标记
bool appleIsReady = false;      //苹果已准备好
bool orangeIsReady = false;     //橘子已准备好
bool plateIsEmpty = true;       //盘子已经空了void father() {int cnt = 1;while (true) {sleep(1);unique_lock<mutex> lck(plate_mtx);plate_cv.wait(lck, []{return plateIsEmpty;});//如果盘子不空,则准备苹果cout << "[Father] : I prepared my " << cnt;switch (cnt) {case 1 : cout << "st ";break;case 2 : cout << "nd ";break;case 3 : cout << "rd ";break;default: cout << "th ";break;}cnt++;cout << "apple." << endl;//修改盘子标记和苹果标记plateIsEmpty = false;appleIsReady = true;plate_cv.notify_all();}
}
void mother() {int cnt = 1;while (true) {sleep(1);unique_lock<mutex> lck(plate_mtx);plate_cv.wait(lck, []{return plateIsEmpty;});//如果盘子不空则准备橘子cout << "\t[Mother] : I prepared my " << cnt;switch (cnt) {case 1 : cout << "st ";break;case 2 : cout << "nd ";break;case 3 : cout << "rd ";break;default: cout << "th ";break;}cnt++;cout << "orange." << endl;//修改盘子标记和橘子标记plateIsEmpty = false;orangeIsReady = true;plate_cv.notify_all();}
}
void son() {while (true) {sleep(1);unique_lock<mutex> lck(plate_mtx);plate_cv.wait(lck, []{return !plateIsEmpty && appleIsReady;});//当盘子不空且苹果已经准备好的情况下拿苹果,并修改标记cout << "\t\t[Son] : I get an apple! Thank you, dad!" << endl;plateIsEmpty = true;appleIsReady = false;plate_cv.notify_all();}
}
void daughter() {while (true) {sleep(1);unique_lock<mutex> lck(plate_mtx);plate_cv.wait(lck, []{return !plateIsEmpty && orangeIsReady;});//当盘子不空且橘子已经准备好的情况下拿橘子,并修改标记cout << "\t\t\t[daughter] : I get an orange! Thank you, mom!" << endl;plateIsEmpty = true;orangeIsReady = false;plate_cv.notify_all();}
}int main() {//创建线程thread father_thread(father);thread mother_thread(mother);thread son_thread(son);thread daughter_thread(daughter);//join所有线程father_thread.join();mother_thread.join();son_thread.join();daughter_thread.join();return 0;
}

在这个代码中,因为每个线程中我们都设置了sleep,所以看起来他可能是按父亲-儿子-母亲-女儿这样的顺序进行的。如果将sleep删去的话,那其实在盘子为空的时候,父亲进程和母亲进程是有相同的概率实现的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/54751.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

树莓派pico上手

0 介绍 不同于作为单板计算机的树莓派5&#xff0c;树莓派 pico 是一款低成本、高性能的微控制器板&#xff0c;具有灵活的数字接口。主要功能包括&#xff1a; 英国树莓派公司设计的 RP2040 微控制器芯片双核 Arm Cortex M0 处理器&#xff0c;弹性的时钟频率高达 133 MHz26…

js 如何监听 body 内容是否改变

如果您想监听body内容的变化&#xff0c;并作出响应&#xff0c;可以使用MutationObserver。以下是一个简单的例子&#xff0c;它会在body内容变化时在控制台输出一条消息&#xff1a; // 创建一个观察者对象 const observer new MutationObserver(function(mutations, obser…

Spring AOP的应用

目录 1、maven坐标配置与xml头配置 2、代理方式的选择与配置 3、AOP的三种配置方式 3.1、XML模式 3.1.1 创建目标类和方法 3.1.2 创建切面 3.1.3 切面xml配置与表达式说明 3.1.4 单测 3.2 纯注解模式 3.2.1 开启注解相关配置 3.2.2 创建目标类和方法 3.2.3 创建切面…

FGPA实验——触摸按键

本文系列都基于正点原子新起点开发板 FPGA系列 1&#xff0c;verlog基本语法&#xff08;随时更新&#xff09; 2&#xff0c;流水灯&#xff08;待定&#xff09; 3&#xff0c;FGPA实验——触摸按键 一、触摸操作原理实现 分类&#xff1a;电阻式&#xff08;不耐用&…

二叉树进阶

目录 1. 二叉搜索树实现 1.1 二叉搜索树概念 2.2 二叉搜索树操作 ​编辑 ​编辑 2.3 二叉搜索树的实现 2.3.0 Destroy() 析构 2.3.1 Insert&#xff08;&#xff09;插入 2.3.2 InOrder&#xff08;&#xff09; 打印搜索二叉树 ​编辑​编辑 2.3.3 Find() 查找 …

el-table表格点击该行任意位置时也勾选上其前面的复选框

需求&#xff1a;当双击表格某一行任意位置时&#xff0c;自动勾选上其前面的复选框 1、在el-table 组件的每一行添加row-dblclick事件&#xff0c;用于双击点击 <el-table:data"tableData"ref"tableRef"selection-change"handleSelectionChange&q…

几种主流的`Content-Type`与其对应的数据格式的例子

application/json: 用于发送和接收JSON格式的数据。例如&#xff0c;可以使用以下代码将JSON数据发送到服务器&#xff1a; $.ajax({url: "/api/endpoint",type: "POST",contentType: "application/json",data: JSON.stringify({ key: "va…

如何在Chrome最新浏览器中调用ActiveX控件?

小编最近登陆工商银行网上银行&#xff0c;发现工商银行的个人网银网页&#xff0c;由于使用了ActiveX安全控件&#xff0c;导致不能用高版本Chrome浏览器打开&#xff0c;目前只有使用IE或基于IE内核的浏览器才能正常登录网上银行&#xff0c;而IE已经彻底停止更新了&#xff…

AI绘图网页版工具

https://chat.bushao.info/?inVitecodeCHBEPQQOOM 一款AI绘图工具&#xff0c;很好玩&#xff0c;推荐&#xff1b; 我自己根据文本生成的图&#xff0c;感觉还不错。

深入理解Java中的序列化与反序列化

目录 1. 引言 2. 什么是序列化&#xff1f; 3. 为什么需要序列化&#xff1f; 4. 如何实现序列化&#xff1f; 5. 示例代码 6. 序列化和反序列化操作 7. 注意事项 8. 拓展&#xff1a;Transient关键字 9. 拓展&#xff1a;序列化的性能优化 10. 结论 1. 引言 在软件…

ROC、TPR、FPR的含义

1、ROC&#xff08;Receiver Operating Characteristic&#xff09; ROC&#xff08;Receiver Operating Characteristic&#xff09;曲线是一种用于评估分类模型性能的工具。它通过绘制真阳性率&#xff08;True Positive Rate, TPR&#xff09;与假阳性率&#xff08;False…

仪表放大器AD620

AD623 是一款低功耗、高精度的仪表放大器&#xff0c;而不是轨到轨运算放大器。它的输入电压范围并不覆盖整个电源电压&#xff08;轨到轨&#xff09;&#xff0c;但在单电源供电下可以处理接近地电位的输入信号。 AD620 和 AD623 都是仪表放大器&#xff0c;但它们在一些关键…

vscode【实用插件】Notes 便捷做笔记

安装 在 vscode 插件市场的搜索 Notes点 安装 安装成功后&#xff0c;vscode 左侧栏会出现 使用 初次使用 需先选择一个本地目录 重启 vscode 后&#xff0c;得到 切换笔记目录 新建笔记 快捷键为 Alt N 默认会创建 .md 文件 配合插件 Markdown Preview Enhanced 预览 .md…

Go语言中的Mutex实现探讨

解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 在并发编程中,互斥锁(Mutex)是一个重要的工具,它帮助我们控制多个协程对共享资源的访问,从而防止数据竞争和不一致性。本文将深入探讨Go语言中Mutex的实现历程和使用方式,同时分享在处理并发问题时的思路与…

Spring Service中的@Service注解的使用

Service注解是Spring框架中用于标识业务逻辑层&#xff08;Service层&#xff09;的注解。它是Spring组件扫描机制的一部分&#xff0c;表明这个类包含业务逻辑&#xff0c;并且应该由Spring容器管理为一个Spring Bean。它与Component类似&#xff0c;都是标识一个类为Spring管…

RestCloud webservice 流程设计

RestCloud webservice 流程设计 操作步骤 离线数据集成&#xff08;首页&#xff09; → \rightarrow → 示例应用数据集成流程&#xff08;边栏&#xff09; → \rightarrow → 所有数据流程 → \rightarrow → webservice节点获取城市列表 → \rightarrow → 流程设计 …

Linux网络——HTTPS详解

文章目录 HTTPS加密 常见加密方式对称加密非对称加密非对称对称数据指纹 证书CA认证数字签名非对称证书对称 中间人 HTTPS 这也是一个应用层协议&#xff0c;是在HTTP协议的基础上引入了一个加密层 为什么要加密呢&#xff0c;这主要是因为如果不对传输主体加密&#xff0c;当…

鹏哥C语言53---第8次作业:函数传参

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <math.h> //--------------------------------------------------------------------------------------------- 第 8 次作业 函数传参等 //-----------------------------------------------------…

2024中国新能源汽车零部件交易会,开源网安展示了什么?

近日&#xff0c;2024中国新能源汽车零部件交易会在十堰国际会展中心举行。开源网安车联网安全实验室携车联网安全相关产品及解决方案亮相本次交易会&#xff0c;保障智能网联汽车“车、路、云、网、图、边”安全&#xff0c;推动智能网联汽车技术突破与产业化发展。 中国新能源…

【深度学习】(7)--神经网络之保存最优模型

文章目录 保存最优模型一、两种保存方法1. 保存模型参数2. 保存完整模型 二、迭代模型 总结 保存最优模型 我们在迭代模型训练时&#xff0c;随着次数初始的增多&#xff0c;模型的准确率会逐渐的上升&#xff0c;但是同时也随着迭代次数越来越多&#xff0c;由于模型会开始学…