C++之生产者和消费者模型分析(条件变量)

2023-05-16

先看一下生产者消费者模型

概述:

生产者把需要处理的数据放到缓存队列中并向消费者发出信号,然后消费者把数据拿出来处理,这里生产者可以是单线程或者多线程,而消费者一般是多线程,消费者线程集合也称 线程池。

下面再举一个生活中的生产者和消费者的例子

例如,在平台接单送外卖,生产者是广大人民,而外卖小哥就是消费者,人发出订单,外卖小哥 接单,当没有订单时,外卖小哥就进入等待状态。

条件变量

条件变量是一种线程同步机制。当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。
C++11的条件变量提供了两个类:
condition_variable:只支持与普通mutex搭配,效率更高。
condition_variable_any:是一种通用的条件变量,可以与任意mutex搭配(包括用户自定义的锁类型)。

成员函数

下面看一个例子,三个线程处理学生,

#include <iostream>
#include <string>
#include <thread>       // 线程类头文件。
#include <mutex>       // 互斥锁类的头文件。
#include <deque>       // deque容器的头文件。
#include <queue>      // queue容器的头文件。
#include <condition_variable>  // 条件变量的头文件。
using namespace std;
class AA
{
    mutex m_mutex;        // 互斥锁。
    condition_variable m_cond;    // 条件变量。
    queue<string, deque<string>> m_q;   // 缓存队列,底层容器用deque。
public:
    void incache(int num)   // 生产数据,num指定数据的个数。
    {
        lock_guard<mutex> lock(m_mutex);   // 申请加锁。
        for (int ii = 0; ii < num; ii++)
        {
            static int bh = 1;    // 同学编号。
            string message = to_string(bh++) + "号同学";    // 拼接出一个数据。
            m_q.push(message);   // 把生产出来的数据入队。
        }
        //给消费者发出信号
        m_cond.notify_one();   // 唤醒一个被当前条件变量阻塞的线程。
        //m_cond.notify_all();    // 唤醒全部被当前条件变量阻塞的线程。
    }
    void outcache()       // 消费者线程任务函数。
    {
        while (true)
        {
            // 把互斥锁转换成unique_lock<mutex>,并申请加锁。
            unique_lock<mutex> lock(m_mutex);   //构造函数的参数时普通互斥锁

            while (m_q.empty())    // 如果队列空,进入循环,否则直接处理数据。必须用循环,不能用if
            {
                m_cond.wait(lock);  // 等待生产者的唤醒信号。
            }

            // 数据元素出队。
            string message = m_q.front();  m_q.pop();
            cout << "线程:" << this_thread::get_id() << "," << message << endl;
            lock.unlock();

            // 处理出队的数据(把数据消费掉)。
            this_thread::sleep_for(chrono::milliseconds(1));   // 假设处理数据需要1毫秒。
        }
    }
};

int main()
{
    AA aa;

    thread t1(&AA::outcache, &aa);     // 创建消费者线程t1。
    thread t2(&AA::outcache, &aa);     // 创建消费者线程t2。
    thread t3(&AA::outcache, &aa);     // 创建消费者线程t3。

    this_thread::sleep_for(chrono::seconds(2));    // 休眠2秒。
    aa.incache(3);      // 生产3个数据。

    this_thread::sleep_for(chrono::seconds(3));    // 休眠3秒。
    aa.incache(5);      // 生产5个数据。

    t1.join();   // 回收子线程的资源。
    t2.join();
    t3.join();
}

我们发现第一次生产者生产了3个对象,但是都由一个线程处理了,第二次生产了5个对象,也是由同一个线程处理了。这是为什么呢?我们发现在第25行信号之通知了一个等待的线程,所以问题解开了,开始申请的3个线程进入等待状态后,程序每次只申请了一个,所以我们只需要改成申请全部就OK了。

即把25行注释掉,把第26行注释打开

此时我们为了研究wait函数的作用,对代码进行如下改动

#include <iostream>
#include <string>
#include <thread>       // 线程类头文件。
#include <mutex>       // 互斥锁类的头文件。
#include <deque>       // deque容器的头文件。
#include <queue>      // queue容器的头文件。
#include <condition_variable>  // 条件变量的头文件。
using namespace std;
class AA
{
    mutex m_mutex;        // 互斥锁。
    condition_variable m_cond;    // 条件变量。
    queue<string, deque<string>> m_q;   // 缓存队列,底层容器用deque。
public:
    void incache(int num)   // 生产数据,num指定数据的个数。
    {
        lock_guard<mutex> lock(m_mutex);   // 申请加锁。
        for (int ii = 0; ii < num; ii++)
        {
            static int bh = 1;    // 同学编号。
            string message = to_string(bh++) + "号同学";    // 拼接出一个数据。
            m_q.push(message);   // 把生产出来的数据入队。
        }
        //m_cond.notify_one();   // 唤醒一个被当前条件变量阻塞的线程。
        m_cond.notify_all();    // 唤醒全部被当前条件变量阻塞的线程。
    }

    void outcache()       // 消费者线程任务函数。
    {
        while (true)
        {
            // 把互斥锁转换成unique_lock<mutex>,并申请加锁。
            cout << "线程:" << this_thread::get_id() << "," << "申请加锁" << endl;
            unique_lock<mutex> lock(m_mutex);   //构造函数的参数时普通互斥锁
            cout << "线程:" << this_thread::get_id() << "," << "加锁成功" << endl;

            //this_thread::sleep_for(chrono::hours(1));   //让线程休眠一小时

             // 条件变量虚假唤醒:消费者线程被唤醒后,缓存队列中没有数据。
            while (m_q.empty())    // 如果队列空,进入循环,否则直接处理数据。必须用循环,不能用if
            {
                m_cond.wait(lock);  // 等待生产者的唤醒信号。
            }//1,把互斥锁解锁  2,阻塞,等待被唤醒 3,给互斥锁加锁 

            // 数据元素出队。
            string message = m_q.front();  m_q.pop();
            cout << "线程:" << this_thread::get_id() << "," << message << endl;
            lock.unlock();

            // 处理出队的数据(把数据消费掉)。
            this_thread::sleep_for(chrono::milliseconds(1));   // 假设处理数据需要1毫秒。
        }
    }
};

int main()
{
    AA aa;

    thread t1(&AA::outcache, &aa);     // 创建消费者线程t1。
    thread t2(&AA::outcache, &aa);     // 创建消费者线程t2。
    thread t3(&AA::outcache, &aa);     // 创建消费者线程t3。


    this_thread::sleep_for(chrono::seconds(2));    // 休眠2秒。
    //aa.incache(3);      // 生产3个数据。
    //aa.incache(2);

    this_thread::sleep_for(chrono::seconds(3));    // 休眠3秒。
    //aa.incache(5);      // 生产5个数据。

    t1.join();   // 回收子线程的资源。
    t2.join();
    t3.join();
}

我们发现每个都加锁成功了,可是事实真的如此吗??

我们打开第37行代码,发现只有一个线程加锁成功,其他的线程都卡在申请加锁那步,所以,wait函数不只是等待生产者信号那么简单,它还有一个功能是把互斥锁解开了,所以后面的线程才能加锁成功。

条件变量的wait()函数

作用

1. 把互斥锁解锁
2. 阻塞,等待被唤醒
3. 给互斥锁加锁

不知道有没有注意到

unique_lock<mutex> lock(m_mutex);   //构造函数的参数时普通互斥锁

为什么要转换为unique_lock锁

unique_lock类

template <class Mutex> class unique_lock是模板类,模板参数为互斥锁类型。
unique_lock和lock_guard都是管理锁的辅助类,都是RAII风格(在构造时获得锁,在析构时释放锁)。它们的区别在于: 为了配合condition_variable,unique_lock还有lock()和unlock()成员函数。

条件变量虚假唤醒问题

消费者线程被唤醒后,缓存队列没有数据

例如在main函数第一次生产2个学生,这是由三个线程,所以有一个线程一定是被虚假唤醒了的。

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

C++之生产者和消费者模型分析(条件变量) 的相关文章

  • 解决Maven配置本地仓库路径不生效问题多个方法详解。(已成功解决自己遇到的问题)

    首先我尝试了很多种方法 xff0c 就是这个方法让我成功 xff0c 和大家分享一下 xff01 xff08 我用方法二成功的 xff01 xff09 maven本地仓库默认值 xff1a 用户家目录 m2 repository 由于本地仓
  • JAVA编写程序,打印九九乘法表(涵盖三种表达形式)

    运用java写出九九乘法表可以概括为三种表达形式 一是长方型 二是正三角型 三是倒三角型 无论是那种 xff0c 用的都是for循环 思路 xff1a 用两个for循环嵌套循环出因子a乘因子b等于乘积 xff0c 外循环代表因子a xff0
  • 【路径规划】蚁群算法机器人栅格地图最短路径规划【含Matlab源码 1618期】

    一 蚁群算法及栅格地图简介 1 蚁群算法 1 1 蚁群算法的提出 蚁群算法 ant colony optimization ACO 又称蚂蚁算法 是一种用来寻找优化路径的机率型算法 它由Marco Dorigo于1992年在他的博士论文中提
  • 遇见Java

    Java是一门面向对象的编程语言 xff0c 不仅吸收了C 43 43 语言的各种优点 xff0c 还摒弃了C 43 43 里难以理解的多继承 指针等概念 xff0c 因此Java语言具有功能强大和简单易用两个特征 Java语言作为静态面向
  • CSS基础-17-拓展-标签居中

    拓展 标签水平居中方法总结 margin 0 auto 如果需要让div p h 大盒子 水平居中 可以通过margin 0 auto 实现 注意点 1 如果需要让 div p h 大盒子 水平居中 xff0c 直接给当前元素本身设置即可
  • 用HTML和css写一个简单地购物小票

    效果图 HTML代码 lt DOCTYPE html gt lt html gt lt head gt lt meta charset 61 34 utf 8 34 gt lt title gt 小票 lt title gt lt link
  • AJAX详解

    1 AJAX是什么 xff1f AJAX即 Asynchronous JavaScript and XML xff08 异步的JavaScript与XML技术 xff09 xff0c 指的是一套综合了多项技术的浏览器端网页开发技术 2 异步
  • 计算机一级必考的10个Excel函数,让我来告诉你

    计算机一级必考的10个Excel函数 xff0c 让我来告诉你 有同学问 xff1a 计算机一级Excel要考哪些函数啊 xff1f xff0c 下图的10个函数就是一级必考的 001 sum求和函数 定义 xff1a 对指定参数进行求和
  • 【一则文章带你了解JavaScript】

    前言 xff1a 想要入门JS xff0c 那我们必须首先了解一下JS的作用 xff0c 我们学习后可以做些什么呢 xff1f 一 首先 xff0c 网页中的表单动态校验以及密码强度的检测是会用到JS的 二 其次 xff0c 我们的网页高级
  • jQuery实现王者荣耀手风琴案例(知识块讲解+案例)

    前言 xff1a 这个案例是几年前的了 xff0c 现在的王者官网是没有这个手风琴模块的 xff0c 我了解到这个案例 xff0c 是受到了黑马程序员知名教师 pink老师的启发 xff0c 我相信大家也都不陌生 xff0c 同样也是我非常
  • 错误问题: Cannot read property ‘XXX‘ of undefined

    undefined不能读取属性XXX 说明定义变量里的值是undefined 查看报错的代码顺藤摸瓜 那打印console看一下 很关键 一定要学会打印 有可能是代码的书写错误了 xff08 大意写错了 xff09 当发现问题后 要学会顺腾
  • Node.js——fs的模块的读取文件-书写文件

    目录 一 fs模块的介绍与初始化 二 fs readFile 读取文件 第二种方法 xff1a 失败演示 xff1a 三 小结 四 fs writeFile 方法向指定文件中写入内容 五 案例 整理用户信息 一 fs模块的介绍与初始化 fs

随机推荐

  • MySQL卸载不干净-MySQL Connector Net xxx文件怎么也删除不了?一招解决它

    相信很多人都遇到过这个问题 xff0c MySQL手动总是卸载不干净 今天没想到让我遇到了 xff0c 那么它就是自讨苦吃 哈哈哈 xff08 这波感谢一下微软 xff0c 为什么 xff1f 大家往下看 xff09 绝招 xff1a 既然
  • uniapp -- 关于uni.navigateTo方法无法跳转的解决方法

    今天做页面跳转的时候遇到了一个问题 xff0c uni navigateTo 方法不是万能的 x1f914 我的需求是在一个component目录下的一个组件中点击遍历出来的组件进入另一个组件 xff0c 我觉得有点绕 xff0c 所以我把
  • Python入门第一章笔记 从安装到编写hello world

    1 下载Python安装包 xff1b 可以到官网下载 xff1a https www python org 但是如果没有翻墙的话 xff0c 下载会很慢 25M安装包 xff0c 需要1个小时以上 如果没有耐心等可以在csdn进行下载 x
  • 谈谈 《 JavaScript - DOM编程艺术 》这本书

    前言 好吧 xff0c 现在已经2023年了 xff0c 对于这本书 xff08 第二版 xff09 来说可能有点老了 xff0c 这本书不是很难理解 xff0c 但也不是很适合新手读 xff0c 当然 xff0c 这本书并不是百宝书 x1
  • 前端程序员 从学校到工作转变的学习历程

    今天是星期一 xff0c 美好而又不是那么美好的上班开始了 xff0c 从学校到职场也有几个月时间了 xff0c 整体来说体验一般般 x1f914 xff0c xff08 请原谅我 这篇文章不配一张图的话 xff0c 会显得文字密密麻麻的
  • 分享项目 - Vue3 + TS + element-ui-plus 项目 -- Table表格表单

    文章目录 前言项目地址以及怎么阅读别人的代码整体代码分页数据作者是怎么处理的 usePagination顺藤摸瓜找到 api 接口的封装api 接口再往底层找全局请求封装与请求拦截器 service ts 前言 今天看一个 ts 项目的 t
  • SpringBoot工程的创建流程

    文章目录 一 初始化时导入的几个基本依赖二 pom文件需要修改的几个地方三 pom文件需要再增加的几个依赖四 maven仓库记得改成自己电脑本地的 xff01 xff01 xff01 五 报错的关于test目录的两个包六 根据数据创建实体类
  • 在matlab中使用find()函数删选数据

    find xff08 xff09 函数 可以查找非零元素的索引和值 拿具体实例来讲 clear clc A 61 1 2 3 456 66 77 343 44 4 3 4 5 A find A 2 gt 61 50 61 删选出A矩阵中第二
  • 【数据结构】二叉树的构建(C语言实现)

    1 树概念及结构 1 1树的概念 树是一种非线性 的数据结构 xff0c 它是由n xff08 n gt 61 0 xff09 个有限结点组成一个具有层次关系的集合 把它叫做树是因 为它看起来像一棵倒挂的树 xff0c 也就是说它是根朝上
  • 解决resultMap报错

    发现问题是使用resultMap来指定类型封装结果的时候出现错误 错误如下 首先排除代码层面的错误 点开错误也没有发现原因 经过查找原来是数据库的字段名和实体的实体名不一致 resultMap则不能自动封装数据 使用reultType 也并
  • Linux防火墙——iptables以及firewalld的使用介绍

    本文基于Linux上CentOS 7版本配合iptables iptables services firewalld等服务进行演示 一 防火墙概念以及Netfilter机制介绍 1 概念 2 防火墙两大类型 3 Netfilter功能 二
  • AOP是什么?如何使用AOP?

    AOP 基本概念是什么 xff1f 什么是AOP xff1f AOP 就是面向切面编程 xff0c 或者叫面向方面编程 xff0c 或者开玩笑的说叫面向方便面编程 在软件业 xff0c AOP为Aspect Oriented Program
  • 情人节浪漫表白,程序员的专属浪漫-----烟花表白

    谁说程序员不懂浪漫 xff1f for birth death love 43 43 do love 43 43 while death 可执行的exe文件我放在文章后面了 xff01 直接双击点开就可以使用了 最终效果 xff1a 运行环
  • 养老产业政策链接

    江西省养老政策文件 xff1a 江西省养老服务条例 http mzw ganzhou gov cn gzsmzjy c103172 202201 d238525b35bb47b49b3de312c9b63a60 shtml 南昌市养老服务体
  • 生产消费模型详解以及代码实现(Java)

    生产消费模型的由来以及定义 xff1a 生产消费者模型是程序设计当中一种非常常见的设计模式 xff0c 被广泛应用于消息队列以及其他的一些场景当中 xff0c xff0c 生产消费模型其中包括三者 xff08 生产者 xff0c 消费者 x
  • java业务代码发送http请求(Post方式:请求参数为JSON格式;Get方式)

    实际开发中 xff0c 可能需要发送http请求到第三方服务获取数据 xff0c 于是就有以下应用 xff1a 依赖 xff1a lt dependency gt lt groupId gt com alibaba lt groupId g
  • IDEA打开终端报错Cannot open Local Terminal命令行功能

    项目场景 xff1a idea项目中不能打开命令行功能 IDEA打开终端报错Cannot open Local Terminal 意思是打开命令行发生错误 idea上配置shell终端 xff0c 命令行页面 问题描述 打开IDEA后 xf
  • 【Linux系统无法连接网络,修改IP地址和网关,ping解决主机不可达的情况】

    文章目录 配置网络 修改状态配置网络 IP地址配置VirtualBox网络修改IP地址修改DNS重启服务的方法修改netplan文件的配置 Ubuntu出现无法连接网络 xff0c ping公网IP地址显示主机不可达 xff0c 打开Fir
  • @vue/eslint-config-standard@6.1.0 from root project

    什么都安装好了 xff0c 但是每次npm i 都报错 xff0c 后来查了资料发现是自己的npm 版本太高 xff0c 切换一下就好了 如下代码 运行后再重新 npm i npm install npm 64 6 14 15 g
  • C++之生产者和消费者模型分析(条件变量)

    先看一下生产者消费者模型 概述 xff1a 生产者把需要处理的数据放到缓存队列中并向消费者发出信号 xff0c 然后消费者把数据拿出来处理 xff0c 这里生产者可以是单线程或者多线程 xff0c 而消费者一般是多线程 xff0c 消费者线