(C++) 多线程之生产者消费者问题
文章目录
- 前言
- Code
- Code
- 运行效果
- 分解讲解
- main()
- class ProducerConsumerProblem
- produce()
- consumer()
- END
前言
生产者消费者问题_百度百科 (baidu.com)
生产者消费者问题 (英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区已经装满时加入数据,消费者也不会在缓冲区为空时消耗数据。
Code
Code
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <vector>constexpr int WORKERTHREAD_COUNT = 3;class ProducerConsumerProblem {
private:// 任务队列std::queue<std::string> m_queue;// mutexstd::mutex m_mutex;// condition_variablestd::condition_variable m_cv;public:/*** 生产者函数*/void produce(const int cnt) {printf("(Produce Thread:%lld) start\n", std::this_thread::get_id());// RAII 自动解锁std::lock_guard<std::mutex> lock(m_mutex);char buff[1024] = {};for (int i = 0; i < cnt; i += 1) {sprintf(buff, "Produce[%02d]", i);m_queue.push(buff);}// 唤醒所有消费者m_cv.notify_all();// 唤醒任意一个消费者// m_cv.notify_one();}/*** 消费者线程*/void consumer() {printf("(Consumer Thread:%lld) start\n", std::this_thread::get_id());constexpr int SLEEP_TIME = 5;std::string receiveBuffer;while (1) {// 比lock_guard更细致的控制力度std::unique_lock<std::mutex> lock(m_mutex);/*** @brief* 如果任务队列为空,则等待** 为什么是循环判断?* >条件变量虚假唤醒:* > 消费者线程被唤醒后* > 缓存队列中没有数据** 条件变量 wait(mutex)的作用* 1. 把互斥锁解开* 2. 阻塞,等待被唤醒* 3. 给互斥锁加锁* 等效写法* m_cv.wait(lock, [this]() { return !m_queue.empty(); });*/while (m_queue.empty()) {// 等待生产者唤醒printf("(Thread:%lld) is waiting ~~~\n",std::this_thread::get_id());m_cv.wait(lock);}// 此时任务队列有数据,且cv被唤醒// 消费者处理一个任务receiveBuffer = m_queue.front();m_queue.pop();// 当前消费者线程获得数据,手动解锁lock.unlock();// 模拟随机设定一个消费时间int solveTimeCost = (rand() % SLEEP_TIME);printf("(Thread:%lld) => %s need %d seconds ...\n",std::this_thread::get_id(), receiveBuffer.c_str(),solveTimeCost);std::this_thread::sleep_for(std::chrono::seconds(solveTimeCost));}}
};int main() {srand(time(0));ProducerConsumerProblem obj;std::vector<std::thread> thdVec;for (int i = 0; i < WORKERTHREAD_COUNT; i += 1) {// 只能move,放入vector延长对象的生存周期auto thd = std::thread(&ProducerConsumerProblem::consumer, &obj);thdVec.push_back(std::move(thd));thdVec[i].detach();}int cnt;// 通过这里的输入阻塞程序while (printf(">>Please input produce number:>\n"), std::cin >> cnt) {obj.produce(cnt);}return 0;
}
运行效果
环境
g++ (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 7.3.0
效果
分别输入2 10 3
>>Please input produce number:>
(Consumer Thread:2) start
(Consumer Thread:3) start
(Consumer Thread:4) start
(Thread:2) is waiting ~~~
(Thread:3) is waiting ~~~
(Thread:4) is waiting ~~~
2
(Produce Thread:1) start
>>Please input produce number:>
(Thread:4) => Produce[00] need 1 seconds ...
(Thread:2) => Produce[01] need 1 seconds ...
(Thread:3) is waiting ~~~
(Thread:4) is waiting ~~~
(Thread:2) is waiting ~~~
10
(Produce Thread:1) start
>>Please input produce number:>
(Thread:2) => Produce[00] need 2 seconds ...
(Thread:4) => Produce[01] need 2 seconds ...
(Thread:3) => Produce[02] need 1 seconds ...
(Thread:3) => Produce[03] need 2 seconds ...
(Thread:2) => Produce[04] need 4 seconds ...
(Thread:4) => Produce[05] need 4 seconds ...
(Thread:3) => Produce[06] need 4 seconds ...
(Thread:2) => Produce[07] need 0 seconds ...
(Thread:2) => Produce[08] need 4 seconds ...
(Thread:4) => Produce[09] need 0 seconds ...
(Thread:4) is waiting ~~~
(Thread:3) is waiting ~~~
(Thread:2) is waiting ~~~
3
(Produce Thread:1) start
>>Please input produce number:>
(Thread:4) => Produce[00] need 4 seconds ...
(Thread:3) => Produce[02] need 0 seconds ...
(Thread:3) is waiting ~~~
(Thread:2) => Produce[01] need 4 seconds ...
(Thread:4) is waiting ~~~
(Thread:2) is waiting ~~~
^Z
分解讲解
main()
int main() {// 设置随机数种子srand(time(0));// 采用对象+成员函数的方式进行多线程ProducerConsumerProblem obj;std::vector<std::thread> thdVec;for (int i = 0; i < WORKERTHREAD_COUNT; i += 1) {// 只能move,放入vector延长对象的生存周期auto thd = std::thread(&ProducerConsumerProblem::consumer, &obj);thdVec.push_back(std::move(thd));thdVec[i].detach();}// 通过手动输入,生产物品int cnt;// 通过这里的输入阻塞程序while (printf(">>Please input produce number:>\n"), std::cin >> cnt) {obj.produce(cnt);}return 0;
}
这里采用对象+成员函数的形式创建线程,因为成员函数的第一个参数是一个隐式的this,因此写std::thread(&ProducerConsumerProblem::consumer, &obj);
注意,std::thread只能move,不能copy。
要将线程对象的生命周期延长,因此移动到vector中。
为了让下文的code执行,将线程进行分离detach()。
class ProducerConsumerProblem
class ProducerConsumerProblem {
private:// 任务队列std::queue<std::string> m_queue;// mutexstd::mutex m_mutex;// condition_variablestd::condition_variable m_cv;public:/*** 生产者函数*/void produce(const int cnt) ;/*** 消费者函数*/void consumer() ;
};
这里的核心数据结构就是std::condition_variable 通常配合std::unique_lock<>使用。
produce()
void produce(const int cnt) {printf("(Produce Thread:%lld) start\n", std::this_thread::get_id());// RAII 自动解锁std::lock_guard<std::mutex> lock(m_mutex);char buff[1024] = {};for (int i = 0; i < cnt; i += 1) {sprintf(buff, "Produce[%02d]", i);m_queue.push(buff);}// 唤醒所有消费者m_cv.notify_all();// 唤醒任意一个消费者// m_cv.notify_one();}
std::lock_guard<std::mutex>可以做到最简单的构造时上锁,析构时解锁。
notify_all()唤醒所有wait中的对象。
notify_one()唤醒一个wait中的对象。
consumer()
void consumer() {printf("(Consumer Thread:%lld) start\n", std::this_thread::get_id());constexpr int SLEEP_TIME = 5;std::string receiveBuffer;while (1) {// 比lock_guard更细致的控制力度std::unique_lock<std::mutex> lock(m_mutex);/// !!!最重要的部分!!!while (m_queue.empty()) {// 等待生产者唤醒printf("(Thread:%lld) is waiting ~~~\n",std::this_thread::get_id());m_cv.wait(lock);}// 此时任务队列有数据,且cv被唤醒// 消费者处理一个任务receiveBuffer = m_queue.front();m_queue.pop();// 当前消费者线程获得数据,手动解锁lock.unlock();// 模拟随机设定一个消费时间int solveTimeCost = (rand() % SLEEP_TIME);printf("(Thread:%lld) => %s need %d seconds ...\n",std::this_thread::get_id(), receiveBuffer.c_str(),solveTimeCost);std::this_thread::sleep_for(std::chrono::seconds(solveTimeCost));}}
std::unique_lock<std::mutex>居于更细致的操作力度,可以手动上锁解锁。
m_cv.wait(lock);整个demo的核心!!!
- 为什么是循环判断?
- 条件变量虚假唤醒:消费者线程被唤醒后,缓存队列中没有数据,重新进入wait
- 条件变量 wait(mutex)的作用
- 把互斥锁解开
- 阻塞,等待被唤醒
- 给互斥锁加锁
在消费者在处理任务时,可以让unique_lock主动解锁。
END
相关文章:
(C++) 多线程之生产者消费者问题
文章目录 前言CodeCode运行效果 分解讲解main()class ProducerConsumerProblemproduce()consumer() END 前言 生产者消费者问题_百度百科 (baidu.com) 生产者消费者问题 (英语:Producer-consumer problem),也称有限缓冲问题&…...
【C语言学习】逃逸字符(转义字符)
逃逸字符(转义字符) 1.\" 双引号 \" printf("请分别输入身高的英尺和英寸,""如输入\"5 7\"表示5英尺7英寸:");这里的"\就是双引号的作用,因为在双引号里面直接用双引号无意义&…...
开发手册|Java后端开发规范重点条目整理
Ps:部分熟知的开发规范未收录在本文中! 一、编程规约 1.1 命名风格 代码中的命名严禁使用拼音与英文混合的方式 alibaba / taobao / youku / hangzhou 等国际通用的名称可视同英文 类名使用大驼峰的形式命名,例如 UpperCameCase 方法、参数…...
c++11 标准模板(STL)(std::basic_ofstream)(二)
定义于头文件 <fstream> template< class CharT, class Traits std::char_traits<CharT> > class basic_ifstream : public std::basic_istream<CharT, Traits> 类模板 basic_ifstream 实现文件流上的高层输入操作。它将 std::basic_istrea…...
k8s概念-pv和pvc
回到目录 kubernetes存储卷的分类太丰富了,每种类型都要写相应的接口与参数才行,这就让维护与管理难度加大。 persistenvolume(PV) 是配置好的一段存储(可以是任意类型的存储卷) 也就是说将网络存储共享出来,配置定义成PV。 PersistentVolumeClaim(PVC)是用户pod使…...
python算法指南程序员经典,python算法教程pdf百度云
大家好,小编来为大家解答以下问题,你也能看懂的python算法书 pdf,python算法教程这本书怎么样,现在让我们一起来看看吧! 给大家带来的一篇关于算法相关的电子书资源,介绍了关于算法、详解、算法基础方面的内…...
微服务使用步骤
Maven的依赖冲突解决方案: 路径最短原则配置优先原则破坏规则则使用排除 SpringBoot场景启动器starter的开发流程 c3p0-spring-boot-starter自定义场景启动器test-c3p0调用自定义场景启动器SpringBoot自动装配SpringBoot应用启动原理nacos服务治理 安装 启动bin/s…...
Ubuntu 23.04 作为系统盘的体验和使用感受
1.为啥主系统装了Ubuntu 由于公司发电脑了,我自己也有一台台式电脑,然后也想去折腾一下Ubuntu,就把自己的笔记本装成Ubuntu系统了, 我使用的是23.04的桌面版,带图形化界面的。我准备换回Windows 11了(因为…...
百分点科技跻身中国智慧应急人工智能解决方案市场前三
近日, 全球领先的IT市场研究和咨询公司IDC发布了《中国智慧应急解决方案市场份额,2022》报告,数据显示,2022年中国智慧应急整体市场为104亿元人民币。其中,智慧应急人工智能解决方案子市场备受关注,百分点科…...
vscode如何退出/切换 github 账号
退出/切换 github 账号 左下角点击头像按钮,选择注销,然后再重新登录...
maven发布到中央仓库
创建账号 https://issues.sonatype.org 【第二步】登录申请新项目 右上角点击Create,Project选择第一项,有的时候带不出来第二个New Project,可以再选一次Project的选项。...
C#IEnumberable<>
在C#中,IEnumerable<>是一个泛型接口,用于表示一个可枚举的集合。它定义了一个用于遍历集合元素的枚举器(enumerator)。通过实现IEnumerable<>接口,我们可以使用foreach语句或LINQ查询等方式来迭代访问集合…...
Flink非对齐checkpoint原理(Flink Unaligned Checkpoint)
Flink非对齐checkpoint原理(Flink Unaligned Checkpoint) 为什么提出Unaligned Checkpoint(UC)? 因为反压严重时会导致Checkpoint失败,可能导致如下问题 恢复时间长-服务效率低非幂等和非事务会导致数据…...
Linux crontab命令:循环执行定时任务(详解)
crontab 命令的基本格式如下: [rootlocalhost ~]# crontab [选项] [file] file 指的是命令文件的名字,表示将 file 作为 crontab 的任务列表文件并载入 crontab,若在命令行中未指定文件名,则此命令将接受标准输入(键盘…...
Linux系统jenkins+newman+postman持续集成环境搭建
1、首先安装nodejs 下载nodejs压缩包,下载地址:nodejs官网下载 建议不用下载最新的,我这里用的是推荐的v12.18版本 下载和解压命令 wget https://nodejs.org/dist/v12.18.3/node-v12.18.3-linux-x64.tar.xz解压安装包(记得没有z&…...
flutter:Future、Stream、RxDart
Future 在Flutter中,Future是Dart语言中的一个类,用于表示异步操作的结果。与Future相关的的重要关键字包括async和await。 async:这个关键字用于在方法或函数声明前添加,以指示该方法为异步方法。在异步方法中,执行…...
Jenkins安装、配置、自动化构建前(nodejs)后端(maven)项目
文章目录 0、Jenkins卸载安装1、Jenkins配置2、后台启动脚本startup.sh3、后台关闭脚本stop.sh4、实时数据启动脚本startup.sh5、实时数据关闭脚本stop.sh6、jenkins制定完任务后,点击立即构建,控制台报错(...无法生成父级目录)7、…...
【网络基础进阶之路】设计网络划分的实战详解
PS:本要求基于华为的eNSP模拟软件进行 具体要求: 完成步骤: 1、对192.168.1.0/24进行子网划分 2、对每一个路由器进行IP的配置 3、开始静态路由的书写,在写之前,我们可以先对每一个路由器写一条通向右边的缺省路由&…...
艺术二维码 API 申请及使用
艺术二维码是一种创新的技术产品,它将二维码与美观的背景图像相结合,创造出既实用又美观的作品。它们不仅具有传统二维码的功能性,能被智能设备快速扫描识别,还加入了艺术元素,增强了视觉吸引力和品牌识别度。其中&…...
JVM GC ROOT分析
GC root原理:通过对枚举GCroot对象做引用可达性分析,即从GC root对象开始,向下搜索,形成的路径称之为 引用链。如果一个对象到GC roots对象没有任何引用,没有形成引用链,那么该对象等待GC回收,换而言之,如果减少内存泄漏,也就是切断引用链,常见的GCRoot对象如下: 1、…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...
