并发编程之生产者消费者模型
什么是生产者消费者模型
生产者消费者模型是多线程中一个比较典型的模型。
打个比方:你是一个客户,你去超市里买火腿肠。
这段话中的 "你"就是消费者, 那么给超市提供火腿肠的供货商就是生产者。超市呢?超市是不是被所有人所共享?大家都可以去访问超市,所以这里的超市是一份临界资源。
所以生产者消费者有三种关系,两种角色,一个交易场所。
三种关系:
1.生产者与生产者
2.消费者与消费者
3.生产者与消费者
生产者与生产者是竞争关系,因为厂商之间互相竞争。所以生产与生产者是互斥关系。
消费者与消费者其实也是竞争关系,但是因为商品够多,而消费者消费速度太慢,所以没有明显的区别。但如果世界上只剩下最后一瓶矿泉水了,那是不是大家都会去抢呢? 所以消费者与消费者其实也是互斥关系。
生产者与消费者也是竞争关系,我们生产者和消费者看成两个线程,超市看成一份临界资源。那么这两个线程是不是都要访问这个临界资源?既然都要访问这个临界资源,那么生产和消费者也是互斥关系。但不仅仅是互斥,因为生产者把超市装满了,是不是要等待用户来消费?同理如果超市空了,消费者是不是要等待生产者来供货?所以生产和消费者还有一层关系,那就是同步。
两种角色
生产者与消费者
一个交易场所
一份临界资源,生产者向临界资源提供数据,消费者从临界资源中拿数据。
有没有发现生产与消费者模型很像管道?没错,管道就是典型的生产者与消费者模型。
这是一个多生产者多消费者的模型。

接下来我们就来实现一个基于阻塞队列的生产者消费者模型。这里的阻塞队列冲当的就是临界资源,生产者把数据放进阻塞队列,消费者把数据从阻塞队列中拿出。
锁的封装
首先我们用RAII风格的锁。
MyLock类
#include<pthread.h>
class MyLock{public:MyLock(pthread_mutex_t* pmtx): _pmtx(pmtx){}void Lock(){ pthread_mutex_lock(_pmtx);}void Unlock() { pthread_mutex_unlock(_pmtx);}private:pthread_mutex_t* _pmtx;};
LockGuard类
#include<pthread.h>
class LockGuard{public:LockGuard(pthread_mutex_t* pmtx):_mtx(pmtx){_mtx.Lock();}~LockGuard(){_mtx.Unlock();}private:MyLock _mtx;};
这个类的构造函数是加锁,析构函数是解锁。所以我们只需要创建一个这个类的对象的代码和临界资源的代码放在一起,就可以实现加锁和解锁了。这种方式可以避免有时候解锁忘记写了导致死锁的问题。
阻塞队列的实现
block_queue类的声明
#include<queue>
#include<pthread.h>
#include<iostream>
#include "Task.hpp"
#include "LockGuard.hpp"
#define DEFAULT_NUM 5
template<class T> //因为不确定阻塞队列放的数据类型, 所以用模板参数class block_queue{private:size_t _num; //阻塞队列的容量std::queue<T> _blockqueue; //阻塞队列pthread_mutex_t _mtx; //锁pthread_cond_t _full; //条件变量,让生产者在阻塞队列为满时进行等待pthread_cond_t _empty; //条件变量,让消费者在阻塞队列为空时进行等待public: block_queue(size_t num = DEFAULT_NUM); //构造函数~block_queue(); // 析构//生产者生产void Push(const T& task);// 消费者消费void Pop(T* out);private://让当前线程在指定的条件变量下等待void Wait(pthread_cond_t* cond) {pthread_cond_wait(cond,&_mtx);}//唤醒指定条件变量下等待的线程void Wakeup(pthread_cond_t* cond) {pthread_cond_signal(cond);}//判断阻塞队列是否满了bool isfull() { return _blockqueue.size() == _num;}//判断阻塞队列是否为空bool isempty() { return _blockqueue.size() == 0;}};
我们的阻塞队列实际上只提供2个操作,一个是push(生产者放数据),一个是pop(消费者拿数据)。
block_queue类的实现
#define DEFAULT_NUM 5
template<class T>class block_queue{private:size_t _num;std::queue<T> _blockqueue; pthread_mutex_t _mtx; pthread_cond_t _full; pthread_cond_t _empty; public: block_queue(size_t num = DEFAULT_NUM) : _num(num){pthread_mutex_init(&_mtx,nullptr);pthread_cond_init(&_full,nullptr);pthread_cond_init(&_empty,nullptr);}~block_queue(){pthread_mutex_destroy(&_mtx);pthread_cond_destroy(&_full);pthread_cond_destroy(&_empty);}//生产者生产void Push(const T& task){LockGuard lockguard(&_mtx); //加锁,出了作用域自动解锁while(isfull()) Wait(&_full); //生产队列已满,生产者在full条件变量下等待//被唤醒后添加任务到生产队列_blockqueue.push(task);printf("%p 生产了一个任务 : %d %c %d\n",pthread_self(),task._x,task._op,task._y); //这是对任务的打印....暂且无视,等Task类实现完后看结果的Wakeup(&_empty); //唤醒消费者}// 消费者消费void Pop(T* out){LockGuard lockguard(&_mtx) ;//加锁,出了作用域自动解锁while(isempty()) Wait(&_empty); //生产队列已空,消费者进入等待 //被唤醒后添加任务到生产队列*out = _blockqueue.front(); //提取任务_blockqueue.pop(); //队列popWakeup(&_full);}private:void Wait(pthread_cond_t* cond) {pthread_cond_wait(cond,&_mtx);}void Wakeup(pthread_cond_t* cond) {pthread_cond_signal(cond);}bool isfull() { return _blockqueue.size() == _num;}bool isempty() { return _blockqueue.size() == 0;}};
Task类实现
我们可以往阻塞队列里面放数据,当然也可以往里面放一个任务。这里我们就创建一个加减乘除取模运算的任务类。
#include <iostream>class Task{public:Task(){}Task(int x, char op,int y):_x(x),_op(op),_y(y),_iserror(false){}void Runing(){int ret = 0;switch(_op){case '+' : ret = _x + _y; break; case '-' : ret = _x - _y; break;case '*' : ret = _x * _y; break;case '/' :{ if(_y) ret = _x / _y;else _iserror = true;break;}case '%' :{ if(_y) ret = _x % _y;else _iserror = true;break;}default: _iserror = true; }if(_iserror) std::cout << "result error" << std::endl; //如果结果错误打印错误else std::cout << _x << _op << _y << "=" << ret << std::endl; //如果结果正确打印完整式子}public:int _x; //第一个操作数char _op; //操作符int _y; //第二个操作数bool _iserror; //结果是否错误};
Main
`
#include "BlockQueue.hpp"
#include <time.h>
#include<unistd.h>
#include<string>#define CONNUM 5
#define PRODNUM 2//生产者放任务
void* ProcuderRuning(void* args)
{wyl::block_queue<wyl::Task>* bq = (wyl::block_queue<wyl::Task>*)args;while(1){int x = rand() % 10 + 1;int y = rand()%20;char op = "+-*/%"[rand() % 5];bq->Push(wyl::Task(x,op,y)); //往阻塞队列中放任务}
}//消费不断拿任务
void* ConsumerRuning(void* args)
{wyl::block_queue<wyl::Task>* bq = (wyl::block_queue<wyl::Task>*)args;while(1){wyl::Task t; bq->Pop(&t); //从阻塞队列中拿任务printf("%p 消费了一个任务",pthread_self());t.Runing(); //处理任务sleep(1); //让消费者不要频繁消费太快,这样阻塞队列满了会等待消费者}
}int main()
{pthread_t con[CONNUM]; pthread_t prod[PRODNUM]; srand((unsigned int)0); //随机数种子//创造等待队列wyl::block_queue<wyl::Task>* bq = new wyl::block_queue<wyl::Task>(5);//创建生产者线程for(int i = 0 ; i < PRODNUM ; i++){std::string name = "prodcuer ";name += std::to_string(i+1); pthread_create(prod + i,nullptr,ProcuderRuning,(void*)bq);}//创建消费者线程for(int i = 0 ; i < CONNUM ; i++){std::string name = "consumer ";name += std::to_string(i+1); pthread_create(con + i,nullptr,ConsumerRuning,(void*)bq);}//等待线程for(int i = 0 ; i < PRODNUM ; i++){pthread_join(prod[i],nullptr);}for(int i = 0 ; i < CONNUM ; i++){pthread_join(con[i],nullptr);}return 0;
}
`
消费者慢消费,生产者快生产的执行结果:

生产者慢生产,消费者快消费的运行结果:

我们会发现,任务井然有序的执行。生产者放了数据后通知消费拿,消费者把数据拿完又会通知生产者放。
相关文章:
并发编程之生产者消费者模型
什么是生产者消费者模型 生产者消费者模型是多线程中一个比较典型的模型。 打个比方:你是一个客户,你去超市里买火腿肠。 这段话中的 "你"就是消费者, 那么给超市提供火腿肠的供货商就是生产者。超市呢?超市是不是被…...
Java要将字符串转换为Map
Java要将字符串转换为Map,可以使用以下方法: import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import java.lang.reflect...
2760. 最长奇偶子数组 --力扣 --JAVA
题目 给你一个下标从 0 开始的整数数组 nums 和一个整数 threshold 。 请你从 nums 的子数组中找出以下标 l 开头、下标 r 结尾 (0 < l < r < nums.length) 且满足以下条件的 最长子数组 : nums[l] % 2 0 对于范围 [l, r - 1] 内的所有下标 i ,…...
JVM——运行时数据区(程序计数器+栈)
目录 1.程序计数器2.栈Java虚拟机栈 - 栈帧的组成1.Java虚拟机栈-局部变量表3.Java虚拟机栈-操作数栈3.Java虚拟机栈-帧数据 3.Java虚拟机栈-栈内存溢出4.本地方法栈 ⚫ Java虚拟机在运行Java程序过程中管理的内存区域,称之为运行时数据区。 ⚫ 《Java虚拟机规范》中…...
【C++】数组中出现次数超过一半的数字
代码: class Solution { public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * param numbers int整型vector * return int整型*/int MoreThanHalfNum_Solution(vector<int>& numbers) {int …...
3GPP协议解读(一)_23.501_23.502_PDU Session_SMF与UDP的交互
UE发起计算服务申请后,网络侧处理的流程 UE发起服务的流程:service request网络侧处理服务涉及的通信数据通过PDU Session进行传输,涉及到SMF与UPF的交互。PDU Session的建立、管理全部由SMF(Session Management Function&#x…...
天池2023智能驾驶汽车虚拟仿真视频数据理解--baseline
baseline 代码 代码 百度飞浆一键运行 import paddle from PIL import Image from clip import tokenize, load_model import glob, json, os import cv2 from PIL import Image from tqdm import tqdm_notebook import numpy as np from sklearn.preprocessing import norma…...
C++入门(1)—命名空间、缺省参数
目录 一、什么是C 1、C关键字(C98) 2、C兼容C 二、C程序预处理指令 三、命名空间 1、命名冲突 第一种: 第二种: 2、域作用限定符 3、实现命名空间 4、命名空间冲突 5、访问命名空间 6、命名空间“std” 四、输入输出 1、定义 2、自动识…...
以程序员的身份使用curl获取速卖通详情
作为一名程序员,我们经常需要和各种API接口打交道。在电商领域,速卖通是一个非常受欢迎的平台。本文将介绍如何使用curl工具通过速卖通的API接口获取商品详情。 一、准备工作 在开始之前,请确保您已完成以下准备工作: 注册速卖…...
Java设计模式-结构型模式-装饰模式
装饰模式 装饰模式角色案例装饰模式与静态代理的区别 装饰模式 允许向一个现有的对象动态地添加新的功能,同时不改变其结构。它是继承的一种替代方案,可以动态地扩展对象。有点像静态代理 角色 装饰者模式有四种角色 抽象被装饰者,被装饰者…...
这7个“小毛病”项目经理必须克服
大家好,我是老原。 项目经理干项目可能不在行,但“踩坑”、“背锅”一定在行。 当上项目经理不容易,当好项目经理更不容易,有永远填不完的坑和背不完的锅。 如果要问项目经理都踩过哪些坑,那真的是太多了࿰…...
一言成文大模型:大模型实践之路
元宇宙_一言成文大模型...
【VSCode】配置C/C++开发环境教程(Windows系统)
下载和配置MinGW编译器 首先,我们需要下载并配置MinGW编译器。 下载MinGW编译器,并将其放置在一个不含空格和中文字符的目录下。 配置环境变量PATH 打开控制面板。可以通过在Windows搜索栏中输入"控制面板"来找到它。 在控制面板中…...
算法实战:亲自写红黑树之四 插入insert的平衡
本文承接自: 算法实战:亲自写红黑树之一-CSDN博客 算法实战:亲自写红黑树之二 完整代码-CSDN博客 算法实战:亲自写红黑树之三 算法详解-CSDN博客 目录 一、入口 二、普通二叉树插入 三、插入后的平衡 四、算法解惑 一、入口 入…...
JWT 技术
一、介绍 JWT全称:JSON Web Token 官网:https://jwt.io/ 定义了一种简洁的、自包含的格式,用于在通信双方以 json 数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的 在生成 JWT 令牌时,会对 JSON 格式的数…...
003.文件描述符、重定向
1、文件描述符 文件描述符是与输入和输出流相关联的整数。最广为人知的文件描述符是stdin、stdout和stderr。我们可以将某个文件描述符的内容重定向到另一个文件描述符中。 在编写脚本的时候会频繁用到标准输入(stdin)、标准输出(stdout&am…...
图论| 827. 最大人工岛 127. 单词接龙
827. 最大人工岛 题目:给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。返回执行此操作后,grid 中最大的岛屿面积是多少? 岛屿 由一组上、下、左、右四个方向相连的 1 形成。 题目链接:[827. 最大人工岛](ht…...
2023年中国恒温蜡疗仪发展趋势分析:应用前景存有很大发展与探索空间[图]
恒温电蜡疗仪可将蜡熔化,利用蜡自身特点,能阻止热的传导、散热慢、气体和水分不易消失,保温性能优越。利用蜡能紧密贴于体表的可塑性,可加入其他药物协同进行治疗,也可将中药与蜡疗有机地结合在一起,产生柔…...
认识“协议”
文章目录: 什么是协议结构化的数据传输序列化和反序列化网络版本计算器 什么是协议 在计算机网络中,协议是指在网络中进行通信和数据交换时,双方遵循的规则和约定集合。它定义了数据的传输格式、顺序、错误处理、认证和安全性等方面的规范。 …...
GO语言的由来与发展历程
Go语言,也称为Golang,是由Google公司的Robert Griesemer、Ken Thompson和Rob Pike三个大牛于2007年开始设计发明,并于2009年正式对外发布的开源编程语言。 三名初始人的目标是设计一种适应网络和多核时代的C语言,Go语言从C继承了…...
LSM6DS3TR-C驱动开发指南:寄存器配置与嵌入式IMU工程实践
1. JoyIT_LSM6DS3TR-C库深度解析:面向嵌入式工程师的LSM6DS3TR-C驱动开发指南LSM6DS3TR-C是意法半导体(STMicroelectronics)推出的超低功耗、高精度6轴惯性测量单元(IMU),集成三轴加速度计与三轴陀螺仪&…...
低空经济落地第一站:工业无人机巡检的格局重构、技术革命与黄金增长期
在海拔4500米的青藏高原特高压输电线路上,一架全自主工业无人机沿着预设航线平稳飞行,以厘米级精度悬停在绝缘子旁,红外热成像镜头精准捕捉到导线的微小发热点,端侧AI大模型实时完成缺陷识别与风险分级,数据同步回传至…...
python-langchain框架(1-8-2 缓存机制——验证缓存的效果)
当用户提出一个常见问题时,首次调用大模型需要经历网络传输、排队等待、模型推理等完整链路,响应时间通常在1至3秒。这个时长已超过人类对“流畅交互”的心理阈值(200毫秒),用户会明显感知到“卡顿”和“等待焦虑”。而…...
Windows环境下Android应用的跨平台解决方案:高效部署与管理指南
Windows环境下Android应用的跨平台解决方案:高效部署与管理指南 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 在Windows环境中部署Android应用通常面临模…...
WarcraftHelper技术适配方案:让经典RTS游戏重获现代硬件支持
WarcraftHelper技术适配方案:让经典RTS游戏重获现代硬件支持 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 痛点解析:魔兽争霸…...
C#实战:基于TouchSocket构建高性能WebSocket双向通信系统
1. WebSocket与TouchSocket核心概念 第一次接触WebSocket时,我被它的双向通信能力惊艳到了。想象一下快递员和收件人的关系:传统HTTP就像每次送货都要重新敲门确认身份(建立连接),而WebSocket则像快递员直接把包裹交给…...
效率提升300%:OpenClaw+Phi-3-vision-128k-instruct重构我的学术工作流
效率提升300%:OpenClawPhi-3-vision-128k-instruct重构我的学术工作流 1. 从手动到自动的学术工作流革命 作为一名每天需要处理大量文献、实验数据和演示材料的科研工作者,我曾经花费近40%的工作时间在重复性文档处理上——截图标注、图表整理、笔记归…...
ai辅助开发新体验:在快马平台用对话创建智能天气应用
最近在做一个天气应用的小项目时,遇到了一个很实际的问题:GitHub经常打不开,导致想参考的开源代码库无法访问。这时候,我发现InsCode(快马)平台的AI辅助开发功能简直是个救星,完全改变了我的开发方式。 需求分析阶段 以…...
AQRC智金未来:全球资产配置的算法革命已来
在康涅狄格州的格林威治(Greenwich),清晨的咖啡总是伴随着全球市场的开盘。这里不仅是顶级对冲机构的“隐居地”,更是AQRC智金未来核心策略的孵化场。很多人习惯于讨论涨跌,但对于智金未来而言,最迷人的数字…...
DLSS Swapper实战指南:高效管理DLSS版本3步达成游戏性能跃升
DLSS Swapper实战指南:高效管理DLSS版本3步达成游戏性能跃升 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 当你在4K分辨率下启动《赛博朋克2077》,满心期待沉浸在夜之城的霓虹中时,…...
