当前位置: 首页 > news >正文

(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)的作用
    1. 把互斥锁解开
    2. 阻塞,等待被唤醒
    3. 给互斥锁加锁

在消费者在处理任务时,可以让unique_lock主动解锁。




END

相关文章:

(C++) 多线程之生产者消费者问题

文章目录 前言CodeCode运行效果 分解讲解main()class ProducerConsumerProblemproduce()consumer() END 前言 生产者消费者问题_百度百科 (baidu.com) 生产者消费者问题 &#xff08;英语&#xff1a;Producer-consumer problem&#xff09;&#xff0c;也称有限缓冲问题&…...

【C语言学习】逃逸字符(转义字符)

逃逸字符&#xff08;转义字符&#xff09; 1.\" 双引号 \" printf("请分别输入身高的英尺和英寸&#xff0c;""如输入\"5 7\"表示5英尺7英寸:");这里的"\就是双引号的作用&#xff0c;因为在双引号里面直接用双引号无意义&…...

开发手册|Java后端开发规范重点条目整理

Ps&#xff1a;部分熟知的开发规范未收录在本文中&#xff01; 一、编程规约 1.1 命名风格 代码中的命名严禁使用拼音与英文混合的方式 alibaba / taobao / youku / hangzhou 等国际通用的名称可视同英文 类名使用大驼峰的形式命名&#xff0c;例如 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存储卷的分类太丰富了,每种类型都要写相应的接口与参数才行&#xff0c;这就让维护与管理难度加大。 persistenvolume(PV) 是配置好的一段存储(可以是任意类型的存储卷) 也就是说将网络存储共享出来,配置定义成PV。 PersistentVolumeClaim(PVC)是用户pod使…...

python算法指南程序员经典,python算法教程pdf百度云

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;你也能看懂的python算法书 pdf&#xff0c;python算法教程这本书怎么样&#xff0c;现在让我们一起来看看吧&#xff01; 给大家带来的一篇关于算法相关的电子书资源&#xff0c;介绍了关于算法、详解、算法基础方面的内…...

微服务使用步骤

Maven的依赖冲突解决方案&#xff1a; 路径最短原则配置优先原则破坏规则则使用排除 SpringBoot场景启动器starter的开发流程 c3p0-spring-boot-starter自定义场景启动器test-c3p0调用自定义场景启动器SpringBoot自动装配SpringBoot应用启动原理nacos服务治理 安装 启动bin/s…...

Ubuntu 23.04 作为系统盘的体验和使用感受

1.为啥主系统装了Ubuntu 由于公司发电脑了&#xff0c;我自己也有一台台式电脑&#xff0c;然后也想去折腾一下Ubuntu&#xff0c;就把自己的笔记本装成Ubuntu系统了&#xff0c; 我使用的是23.04的桌面版&#xff0c;带图形化界面的。我准备换回Windows 11了&#xff08;因为…...

百分点科技跻身中国智慧应急人工智能解决方案市场前三

近日&#xff0c; 全球领先的IT市场研究和咨询公司IDC发布了《中国智慧应急解决方案市场份额&#xff0c;2022》报告&#xff0c;数据显示&#xff0c;2022年中国智慧应急整体市场为104亿元人民币。其中&#xff0c;智慧应急人工智能解决方案子市场备受关注&#xff0c;百分点科…...

vscode如何退出/切换 github 账号

退出/切换 github 账号 左下角点击头像按钮&#xff0c;选择注销&#xff0c;然后再重新登录...

maven发布到中央仓库

创建账号 https://issues.sonatype.org 【第二步】登录申请新项目 右上角点击Create&#xff0c;Project选择第一项&#xff0c;有的时候带不出来第二个New Project&#xff0c;可以再选一次Project的选项。...

C#IEnumberable<>

在C#中&#xff0c;IEnumerable<>是一个泛型接口&#xff0c;用于表示一个可枚举的集合。它定义了一个用于遍历集合元素的枚举器&#xff08;enumerator&#xff09;。通过实现IEnumerable<>接口&#xff0c;我们可以使用foreach语句或LINQ查询等方式来迭代访问集合…...

Flink非对齐checkpoint原理(Flink Unaligned Checkpoint)

Flink非对齐checkpoint原理&#xff08;Flink Unaligned Checkpoint&#xff09; 为什么提出Unaligned Checkpoint&#xff08;UC&#xff09;&#xff1f; 因为反压严重时会导致Checkpoint失败&#xff0c;可能导致如下问题 恢复时间长-服务效率低非幂等和非事务会导致数据…...

Linux crontab命令:循环执行定时任务(详解)

crontab 命令的基本格式如下&#xff1a; [rootlocalhost ~]# crontab [选项] [file] file 指的是命令文件的名字&#xff0c;表示将 file 作为 crontab 的任务列表文件并载入 crontab&#xff0c;若在命令行中未指定文件名&#xff0c;则此命令将接受标准输入&#xff08;键盘…...

Linux系统jenkins+newman+postman持续集成环境搭建

1、首先安装nodejs 下载nodejs压缩包&#xff0c;下载地址&#xff1a;nodejs官网下载 建议不用下载最新的&#xff0c;我这里用的是推荐的v12.18版本 下载和解压命令 wget https://nodejs.org/dist/v12.18.3/node-v12.18.3-linux-x64.tar.xz解压安装包&#xff08;记得没有z&…...

flutter:Future、Stream、RxDart

Future 在Flutter中&#xff0c;Future是Dart语言中的一个类&#xff0c;用于表示异步操作的结果。与Future相关的的重要关键字包括async和await。 async&#xff1a;这个关键字用于在方法或函数声明前添加&#xff0c;以指示该方法为异步方法。在异步方法中&#xff0c;执行…...

Jenkins安装、配置、自动化构建前(nodejs)后端(maven)项目

文章目录 0、Jenkins卸载安装1、Jenkins配置2、后台启动脚本startup.sh3、后台关闭脚本stop.sh4、实时数据启动脚本startup.sh5、实时数据关闭脚本stop.sh6、jenkins制定完任务后&#xff0c;点击立即构建&#xff0c;控制台报错&#xff08;...无法生成父级目录&#xff09;7、…...

【网络基础进阶之路】设计网络划分的实战详解

PS&#xff1a;本要求基于华为的eNSP模拟软件进行 具体要求&#xff1a; 完成步骤&#xff1a; 1、对192.168.1.0/24进行子网划分 2、对每一个路由器进行IP的配置 3、开始静态路由的书写&#xff0c;在写之前&#xff0c;我们可以先对每一个路由器写一条通向右边的缺省路由&…...

艺术二维码 API 申请及使用

艺术二维码是一种创新的技术产品&#xff0c;它将二维码与美观的背景图像相结合&#xff0c;创造出既实用又美观的作品。它们不仅具有传统二维码的功能性&#xff0c;能被智能设备快速扫描识别&#xff0c;还加入了艺术元素&#xff0c;增强了视觉吸引力和品牌识别度。其中&…...

JVM GC ROOT分析

GC root原理:通过对枚举GCroot对象做引用可达性分析,即从GC root对象开始,向下搜索,形成的路径称之为 引用链。如果一个对象到GC roots对象没有任何引用,没有形成引用链,那么该对象等待GC回收,换而言之,如果减少内存泄漏,也就是切断引用链,常见的GCRoot对象如下: 1、…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

【git】把本地更改提交远程新分支feature_g

创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

HTML前端开发:JavaScript 获取元素方法详解

作为前端开发者&#xff0c;高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法&#xff0c;分为两大系列&#xff1a; 一、getElementBy... 系列 传统方法&#xff0c;直接通过 DOM 接口访问&#xff0c;返回动态集合&#xff08;元素变化会实时更新&#xff09;。…...

DBLP数据库是什么?

DBLP&#xff08;Digital Bibliography & Library Project&#xff09;Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高&#xff0c;数据库文献更新速度很快&#xff0c;很好地反映了国际计算机科学学术研…...

深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向

在人工智能技术呈指数级发展的当下&#xff0c;大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性&#xff0c;吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型&#xff0c;成为释放其巨大潜力的关键所在&…...