c++线程同步之条件变量

news2024/12/23 17:51:50

c++线程同步之条件变量

​ 条件变量是C++11提供的另外一种用于等待的同步机制,它能阻塞一个或多个线程,直到收到另外一个线程发出的通知或者超时时,才会唤醒当前阻塞的线程。条件变量需要和互斥量配合起来使用,C++11提供了两种条件变量:

  • condition_variable:需要配合std::unique_lock<std::mutex>进行wait操作,也就是阻塞线程的操作。
  • condition_variable_any:可以和任意带有lock()、unlock()语义的mutex搭配使用,也就是说有四种:
    1. std::mutex:独占的非递归互斥锁
    2. std::timed_mutex:带超时的独占非递归互斥锁
    3. std::recursive_mutex:不带超时功能的递归互斥锁
    4. std::recursive_timed_mutex:带超时的递归互斥锁

条件变量通常用于生产者和消费者模型,大致使用过程如下:

  1. 拥有条件变量的线程获取互斥量
  2. 循环检查某个条件,如果条件不满足阻塞当前线程,否则线程继续向下执行
    • 产品的数量达到上限,生产者阻塞,否则生产者一直生产。。。
    • 产品的数量为零,消费者阻塞,否则消费者一直消费。。。
  3. 条件满足之后,可以调用notify_one()或者notify_all()唤醒一个或者所有被阻塞的线程
    • 由消费者唤醒被阻塞的生产者,生产者解除阻塞继续生产。。。
    • 由生产者唤醒被阻塞的消费者,消费者解除阻塞继续消费。。。

1.condition_variable

1.1成员函数

condition_variable的成员函数主要分为两部分:线程等待(阻塞)函数 和线程通知(唤醒)函数,这些函数被定义于头文件 <condition_variable>

等待函数

wait() wait_for() wait_until()

调用wait()函数的线程会被阻塞

// ①
void wait (unique_lock<mutex>& lck);
// ②
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);
  • 函数①:调用该函数的线程直接被阻塞

  • 函数②:该函数的第二个参数是一个判断条件,是一个返回值为布尔类型的函数

    • 该参数可以传递一个有名函数的地址,也可以直接指定一个匿名函数

    • 表达式返回false当前线程被阻塞,表达式返回true当前线程不会被阻塞,继续向下执行

  • 独占的互斥锁对象不能直接传递给wait()函数,需要通过模板类unique_lock进行二次处理,通过得到的对象仍然可以对独占的互斥锁对象做如下操作,使用起来更灵活。

在这里插入图片描述

  • 如果线程被该函数阻塞,这个线程会释放占有的互斥锁的所有权,当阻塞解除之后这个线程会重新得到互斥锁的所有权,继续向下执行这个过程是在函数内部完成的,了解这个过程即可,其目的是为了避免线程的死锁)。

wait_for()函数和wait()的功能是一样的,只不过多了一个阻塞时长,假设阻塞的线程没有被其他线程唤醒,当阻塞时长用完之后,线程就会自动解除阻塞,继续向下执行。

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

wait_until()函数和wait_for()的功能是一样的,它是指定让线程阻塞到某一个时间点,假设阻塞的线程没有被其他线程唤醒,当到达指定的时间点之后,线程就会自动解除阻塞,继续向下执行

template <class Clock, class Duration>
cv_status wait_until (unique_lock<mutex>& lck,
                      const chrono::time_point<Clock,Duration>& abs_time);

template <class Clock, class Duration, class Predicate>
bool wait_until (unique_lock<mutex>& lck,
                 const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);
通知函数
void notify_one() noexcept;
void notify_all() noexcept;
  • notify_one():唤醒一个被当前条件变量阻塞的线程
  • notify_all():唤醒全部被当前条件变量阻塞的线程
1.2生产者和消费者模型
#include <iostream>
using namespace std;
#include <condition_variable>
#include <thread>
#include <queue>
#include <mutex>
#include <chrono>

// 锁对象
mutex bufferMutex;
// 条件变量
condition_variable bufferNotFull, bufferNotEmpty;
// 队列
queue<int> buffer;
// 缓冲区大小
const int bufferSize = 5;

void producer(int id) {
    for (int i = 0; i < 10; ++i) {
        unique_lock<mutex> lock(bufferMutex);
        // 检查缓冲区是否已满,如果满则等待
        bufferNotFull.wait(lock, []() { return buffer.size() < bufferSize; });

        // 生产数据并放入缓冲区
        int data = i;
        buffer.push(data);
        cout << "Producer " << id << " produced data: " << data << endl;

        // 通知消费者线程缓冲区非空
        bufferNotEmpty.notify_one();
        lock.unlock();
        this_thread::sleep_for(chrono::milliseconds(10));
    }
}

void consumer(int id) {
    for (int i = 0; i < 10; i++) {
        unique_lock<mutex> lock(bufferMutex);
        // 检查缓冲区是否为空,如果空则等待
        bufferNotEmpty.wait(lock, []() { return buffer.size() > 0; });
        int data = buffer.front();
        buffer.pop();
        cout << "Consumer " << id << " consumed data: " << data << endl;

        // 通知生产者缓冲区不满
        bufferNotFull.notify_one();
        lock.unlock();
        this_thread::sleep_for(chrono::milliseconds(10));
    }

}

int main() {
    thread producerThread1(producer, 1);
    thread producerThread2(producer, 2);
    thread consumerThread1(consumer, 1);
    thread consumerThread2(consumer, 2);

    producerThread1.join();
    producerThread2.join();
    consumerThread1.join();
    consumerThread2.join();
    return 0;
}

程序输出结果:

Producer 2 produced data: 0
Producer 1 produced data: 0
Consumer 1 consumed data: 0
Consumer 2 consumed data: 0
Producer 2 produced data: 1
Consumer 2 consumed data: 1
Producer 1 produced data: 1
Consumer 1 consumed data: 1
Producer 1 produced data: 2
Consumer 2 consumed data: 2
Producer 2 produced data: 2
Consumer 1 consumed data: 2
Producer 1 produced data: 3
Consumer 2 consumed data: 3
Producer 2 produced data: 3
Consumer 1 consumed data: 3
Producer 1 produced data: 4
Consumer 2 consumed data: 4
Producer 2 produced data: 4
Consumer 1 consumed data: 4
Producer 1 produced data: 5
Consumer 1 consumed data: 5
Producer 2 produced data: 5
Consumer 2 consumed data: 5
Producer 2 produced data: 6
Producer 1 produced data: 6
Consumer 2 consumed data: 6
Consumer 1 consumed data: 6
Producer 2 produced data: 7
Consumer 2 consumed data: 7
Producer 1 produced data: 7
Consumer 1 consumed data: 7
Producer 1 produced data: 8
Producer 2 produced data: 8
Consumer 2 consumed data: 8
Consumer 1 consumed data: 8
Producer 1 produced data: 9
Producer 2 produced data: 9
Consumer 1 consumed data: 9
Consumer 2 consumed data: 9

在这个例子中:

  • bufferMutex 是一个互斥量,用于保护对缓冲区的并发访问。
  • bufferNotEmptybufferNotFull 是条件变量,用于在缓冲区非空和非满的情况下进行线程同步。
  • 生产者线程使用 bufferNotFull.wait 来等待缓冲区非满,消费者线程使用 bufferNotEmpty.wait 来等待缓冲区非空。
  • 在生产者放入数据后,会通过 bufferNotEmpty.notify_one() 通知等待的消费者线程。
  • 在消费者取出数据后,会通过 bufferNotFull.notify_one() 通知等待的生产者线程。

这个模型使用了条件变量来实现生产者和消费者线程之间的同步和通信,确保生产者在缓冲区非满时生产数据,而消费者在缓冲区非空时消费数据。这有助于避免竞态条件和提高程序的效率。

2.condition_variable_any

2.1成员函数

condition_variable_any的成员函数也是分为两部分:线程等待(阻塞)函数 和线程通知(唤醒)函数,这些函数被定义于头文件 <condition_variable>。

等待函数
// ①
template <class Lock> void wait (Lock& lck);
// ②
template <class Lock, class Predicate>
void wait (Lock& lck, Predicate pred);
  • 函数①:调用该函数的线程直接被阻塞
  • 函数②:该函数的第二个参数是一个判断条件,是一个返回值为布尔类型的函数
    • 该参数可以传递一个有名函数的地址,也可以直接指定一个匿名函数
    • 表达式返回false当前线程被阻塞,表达式返回true当前线程不会被阻塞,继续向下执行
  • 可以直接传递给wait()函数的互斥锁类型有四种,分别是:
    • std::mutex、std::timed_mutex、std::recursive_mutex、std::recursive_timed_mutex
  • 如果线程被该函数阻塞,这个线程会释放占有的互斥锁的所有权,当阻塞解除之后这个线程会重新得到互斥锁的所有权,继续向下执行(这个过程是在函数内部完成的,了解这个过程即可,其目的是为了避免线程的死锁)。

wait_for()函数和wait()的功能是一样的,只不过多了一个阻塞时长,假设阻塞的线程没有被其他线程唤醒,当阻塞时长用完之后,线程就会自动解除阻塞,继续向下执行。

template <class Lock, class Rep, class Period>
cv_status wait_for (Lock& lck, const chrono::duration<Rep,Period>& rel_time);
	
template <class Lock, class Rep, class Period, class Predicate>
bool wait_for (Lock& lck, const chrono::duration<Rep,Period>& rel_time, Predicate pred);

wait_until()函数和wait_for()的功能是一样的,它是指定让线程阻塞到某一个时间点,假设阻塞的线程没有被其他线程唤醒,当到达指定的时间点之后,线程就会自动解除阻塞,继续向下执行。

template <class Lock, class Clock, class Duration>
cv_status wait_until (Lock& lck, const chrono::time_point<Clock,Duration>& abs_time);

template <class Lock, class Clock, class Duration, class Predicate>
bool wait_until (Lock& lck, 
                 const chrono::time_point<Clock,Duration>& abs_time, 
                 Predicate pred);
通知函数
void notify_one() noexcept;
void notify_all() noexcept;
  • notify_one():唤醒一个被当前条件变量阻塞的线程
  • notify_all():唤醒全部被当前条件变量阻塞的线程
2.2生产者和消费者模型

只需要改一下代码即可

#include <iostream>
using namespace std;
#include <condition_variable>
#include <thread>
#include <queue>
#include <mutex>
#include <chrono>

// 锁对象
mutex bufferMutex;
// 条件变量
condition_variable_any bufferNotFull, bufferNotEmpty;
// 队列
queue<int> buffer;
// 缓冲区大小
const int bufferSize = 5;

void producer(int id) {
    for (int i = 0; i < 10; ++i) {
        // unique_lock<mutex> lock(bufferMutex);
        lock_guard<mutex> lock(bufferMutex);
        // 检查缓冲区是否已满,如果满则等待
        bufferNotFull.wait(bufferMutex, []() { return buffer.size() < bufferSize; });

        // 生产数据并放入缓冲区
        int data = i;
        buffer.push(data);
        cout << "Producer " << id << " produced data: " << data << endl;

        // 通知消费者线程缓冲区非空
        bufferNotEmpty.notify_one();
        // lock.unlock();
    }
}

void consumer(int id) {
    for (int i = 0; i < 10; i++) {
        // unique_lock<mutex> lock(bufferMutex);
        lock_guard<mutex> lock(bufferMutex);
        // 检查缓冲区是否为空,如果空则等待
        bufferNotEmpty.wait(bufferMutex, []() { return buffer.size() > 0; });
        int data = buffer.front();
        buffer.pop();
        cout << "Consumer " << id << " consumed data: " << data << endl;

        // 通知生产者缓冲区不满
        bufferNotFull.notify_one();
        // lock.unlock();
    }

}

int main() {
    thread producerThread1(producer, 1);
    thread producerThread2(producer, 2);
    thread consumerThread1(consumer, 1);
    thread consumerThread2(consumer, 2);

    producerThread1.join();
    producerThread2.join();
    consumerThread1.join();
    consumerThread2.join();
    return 0;
}

程序运行结果:

Producer 1 produced data: 0
Producer 1 produced data: 1
Producer 1 produced data: 2
Producer 1 produced data: 3
Producer 1 produced data: 4
Consumer 1 consumed data: 0
Consumer 1 consumed data: 1
Consumer 1 consumed data: 2
Consumer 1 consumed data: 3
Consumer 1 consumed data: 4
Producer 1 produced data: 5
Producer 1 produced data: 6
Producer 1 produced data: 7
Producer 1 produced data: 8
Producer 1 produced data: 9
Consumer 2 consumed data: 5
Consumer 2 consumed data: 6
Consumer 2 consumed data: 7
Consumer 2 consumed data: 8
Consumer 2 consumed data: 9
Producer 2 produced data: 0
Producer 2 produced data: 1
Producer 2 produced data: 2
Producer 2 produced data: 3
Producer 2 produced data: 4
Consumer 1 consumed data: 0
Consumer 1 consumed data: 1
Consumer 1 consumed data: 2
Consumer 1 consumed data: 3
Consumer 1 consumed data: 4
Producer 2 produced data: 5
Producer 2 produced data: 6
Producer 2 produced data: 7
Producer 2 produced data: 8
Producer 2 produced data: 9
Consumer 2 consumed data: 5
Consumer 2 consumed data: 6
Consumer 2 consumed data: 7
Consumer 2 consumed data: 8
Consumer 2 consumed data: 9

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

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

相关文章

Linux --绘制地图投影出现报错:无法成功下载地图背景数据

Linux --绘制地图投影出现报错&#xff1a;无法成功下载地图背景数据 主要原因是由于使用学院集群&#xff0c;该集群无法连接外网&#xff0c;在使用cartopy绘制地图投影时&#xff0c;导致无法成功加载地图背景数据解决方法也很简单&#xff0c;自己手动下载所需要的地形数据…

2023年【焊工(初级)】报名考试及焊工(初级)考试总结

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 焊工&#xff08;初级&#xff09;报名考试考前必练&#xff01;安全生产模拟考试一点通每个月更新焊工&#xff08;初级&#xff09;考试总结题目及答案&#xff01;多做几遍&#xff0c;其实通过焊工&#xff08;初…

案例041:基于微信小程序的私家车位共享系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

c++语法学习

C学习记录&#xff09; vs的安装代码的语法注释单行注释多行注释 main变量变量的定义整数类型浮点数字符型bool类型的定义sizeof(计算变量大小) 常量的定义defineconst 转义字符数据的输入cin 运算符算数运算符赋值运算符比较运算符逻辑运算符 程序流程结构顺序结构选择结构ifs…

Redis课程:黑马点评

文章目录 基于Redis实现短信登录商户查询缓存优惠券秒杀一人一单 分布式锁Redis分布式锁误删情况说明解决Redis分布式锁误删问题使用lua脚本解决分布式锁的原子性问题 基于阻塞队列实现秒杀优化Redis消息队列优化秒杀业务达人探店参考 本文是根据黑马程序员的视频课程 黑马程序…

利用transition-group标签包裹li标签,实现输入数据后按Enter键将数据添加到列表中

1.效果图 2.代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title></title><script src"https://cdn.bootcdn.net/ajax/libs/vue/2.3.0/vue.js"></script><div id&quo…

C#基础——语法学习

C#的基本语法 在介绍基本语法之前我们先来大概讲一下创建好的这些文件都是做什么的 .sln文件&#xff1a;将项目和解决方案项结合到一起 .vs文件夹&#xff1a;用来存储当前解决方案中关于用户的设置和自定义项&#xff0c;比如断点&#xff0c;主题等。&#xff08;一般都将其…

Swin UNetR:把 UNet 和 Swin Transformer 结合

Swin UNetR&#xff1a;把 UNet 和 Swin Transformer 结合 网络结构使用指南 前置知识&#xff1a;Swin Transformer&#xff1a;将卷积网络和 Transformer 结合 Swin UNetR 结合 Swin Transformer 的上下文建模能力和 U-Net 的像素级别预测能力&#xff0c;提高语义分割任务的…

2010年全国地质灾害隐患点数据,shp/excel格式,含灾害类型、等级、经纬度坐标等字段

基本信息. 数据名称: 全国地质灾害隐患点数据 数据格式: Shp、Excel 数据时间: 2010年 数据几何类型: 点 数据坐标系: WGS84坐标系 数据来源&#xff1a;网络公开数据 数据字段&#xff1a; 序号字段名称字段说明1xzqhdm行政区划代码2xzqhmc行政区划名称3mc名称4z…

Halcon一维码识别

文章目录 参数连接halcon 自带案例1&#xff08;设置校验位识别条码&#xff09;Halcon 自带案例2&#xff08;设置对比度识别条码&#xff09;Halcon 自带案例3&#xff08;存在曲面变形&#xff09;Halcon 自带案例4&#xff08;设置条码扫描线&#xff09;Halcon 自带案例5&…

HarmonyOS第一课ArkTS开发语言(TypeScript快速入门)

编程语言介绍 ArkTS是HarmonyOS优选的主力应用开发语言。它在TypeScript&#xff08;简称TS&#xff09;的基础上&#xff0c;匹配ArkUI框架&#xff0c;扩展了声明式UI、状态管理等相应的能力&#xff0c;让开发者以更简洁、更自然的方式开发跨端应用。要了解什么是ArkTS&…

解决设备维修管理问题,易点易动来帮忙!

设备维修管理常常存在一些问题&#xff0c;给企业带来不便和困扰&#xff1a; 1&#xff09;维修信息不及时准确&#xff0c;导致维修延误或错过重要维护时机&#xff1b; 2&#xff09;纸质记录容易丢失或难以管理&#xff0c;使得维修历史不完整&#xff1b; 3&#xff09…

MISC之LSB

LSB隐写 简介 LSB隐写&#xff08;Least Significant Bit Steganography&#xff09;是一种隐写术&#xff0c;它通过将秘密信息嵌入到图像、音频或视频等多媒体文件中的最低有效位中来隐藏信息。在数字图像中&#xff0c;每个像素由红、绿、蓝三个通道的颜色值组成。每个颜色…

c语言插入排序算法(详解)

插入排序是一种简单直观的排序算法&#xff0c;其主要思想是将一个待排序的元素插入到已经排好序的部分的合适位置。 插入排序的原理如下&#xff1a; 将序列分为两部分&#xff1a;已排序部分和未排序部分。初始时&#xff0c;已排序部分只包含第一个元素&#xff0c;未排序…

Unity 射线检测(Raycast)检测图层(LayerMask)的设置

目录 主要内容 拓展&#xff1a; 主要内容 Raycast函数有很多重载(函数的重载根据函数的参数来决定) 这里只涉及这个重载,其余重载可以很方便得在Visual Studio中看源码获取&#xff1b; public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hit…

成绩统计(oj题)

一道考验细节的题 最后是&#xff1f;&#xff1a;运算符用错了 代码如下&#xff1a; #include<stdio.h> #include<string.h> typedef struct Grade{int num;int inv; }Grade; Grade tmp[10]; int n, m, g, interval[10] {0};int main(void) {scanf("%d%d…

智能优化算法应用:基于鸟群算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于鸟群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于鸟群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鸟群算法4.实验参数设定5.算法结果6.参考文献7.MA…

Vue3-15-事件处理的基本使用详解

什么是事件处理 事件处理 &#xff1a; 就是对页面上的事件进行捕获并进行逻辑上的处理。 例如 &#xff1a; 点击了一个按钮&#xff0c;捕获点击事件&#xff0c;并进行响应的逻辑处理。vue3中的事件处理的语法 主要使用到的是 v-on 指令&#xff0c; 这个指令的语法糖&…

【教程】如何将重要文件进行混淆和加密

怎么保护苹果手机移动应用程序ipa中文件安全&#xff1f; ios应用程序存储一些图片&#xff0c;资源&#xff0c;配置信息&#xff0c;甚至敏感数据如用户信息、证书、私钥等。这些数据怎么保护呢&#xff1f;可以使用iOS提供的Keychain来保护敏感数据&#xff0c;也可以使用加…

机器学习---TF-IDF算法

1、TF-IDF TF-IDF(Term Frequency-Inverse Document Frequency, 词频-逆文本频率)。TF指词频&#xff0c;IDF指的是逆文本频率。TF-IDF是一种用于信息检索与数据挖掘的常用加权技术&#xff0c;可以评估一个词在一个文件集或者一个语料库中对某个文件的重要程度。一个词语在一篇…