(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、…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...