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

40 生产者消费者模型

生产者消费者模型

概念

为何要使用生产者消费者模型,这个是用过一个容器解决生产者和消费的强耦合问题。生产者和消费者之间不需要通讯,通过阻塞队列通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列取,阻塞队列相当于缓冲区,平衡了双方的能力,用来解耦的
在这里插入图片描述

上面超市的例子。消费者需要泡面的话不用去找供货商要货,而是去超市取。如果找供货商,消费者只需要一包,供货商开启生产设备只生产一包,多次这样很浪费效率也不高。超市作为存储,需要一万包泡面,供货商生产1万包摆到超市里,将超市塞满,缓存起来,调整供货商和消费者的速度不一致导致的效率问题。供货商就可以休息下来。消费者需要几包去超市取,支持了一种忙闲不均的状态。供货商关注超市有多少空位置,需要多少货,消费者关注现有商品的数量。供货商在生产的时候,和消费者没关系,消费者购买的时候和供应商也没关系,双方不需要互相考虑,只完成自己的事情,就减少了依赖性,解耦。

在计算机里,生产者和消费者都是由线程承担,超市是一种特殊结构的内存空间,这个结构是一种共享资源,整个过程就是执行流在通信,如何安全高效的通信。共享资源就有并发的问题,这种并发有三种关系:

生产者和生产者:互斥关系。一个在供货的时候另一个需要等待
生产者和消费者:互斥和同步关系。如果供货商正在摆一个商品,消费者有没有得到。有一种不确定性,生产者要确定,数据安全,只有生产了和没有生产,消费者一定可以得到货物。供货商不停联系超市需不需要货,超市已经满了还在不停询问,占用了消费者询问的机会,导致消费者饥饿问题。所以要同步,保证顺序性。供货商刚供货一次再询问时,告知一段时间之内不要询问,消费者询问没有商品时,也告知没有并一段时间不要询问,安全才是本质
消费者和消费者:互斥关系

321原则
3种关系2种角色1个交易场所
3种关系,生产者和消费者之间互相搭配
2种角色:生产和消费
1个交易场所:特定结构的内存空间

优点:
1.支持忙闲不均
2.支持并发
3.生产和消费进行解耦

什么是解耦。main函数内调用一个函数,传入参数,需要等到函数返回才能继续往下执行,可以将两个分开为线程,参数用一段空间缓存,放到缓冲区里,函数调用时在空间里取,这就是解耦

基于BlockingQueue的生产者模型

BlockingQueue

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)
类似于管道

在这里插入图片描述

类的设计
首先需要数据存储的结构,这个用队列,一个容量设置为队列允许存放的最多数量。对队列的访问同一时间只能有一方,所以需要一个锁。类提供存入数据和取出数据的功能,生产者关心的是还能放多少数据,如果大于最大容量就要停止,所以要判断队列的现有数量,这是对共享资源的访问,加锁和释放锁,判断大于容量时就去条件变量队列等待,同样,消费者取物品也需要一个条件变量,消费者判断有没有商品,没有就到消费者的条件变量等待。当生产者生产出一个商品放入后就唤醒消费者取,消费者取完唤醒生产者生产

#include <queue>
#include <pthread.h>template<class T>
class BlockQueue
{static const int defaultnum = 20;
public:BlockQueue(int cap = defaultnum){_maxcap = cap;pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_pcond, nullptr);pthread_cond_init(&_ccond, nullptr);_lowwater = _maxcap / 3;_highwater = _maxcap / 3 * 2;}void push(const T& x){pthread_mutex_lock(&_mutex);if (_que.size() == _maxcap) //防止被伪唤醒的状态 {pthread_cond_wait(&_pcond, &_mutex); //调用,自动释放锁}_que.push(x);  //确保生产条件满足才能生产//if (_que.size() > _highwater)pthread_cond_signal(&_ccond);pthread_mutex_unlock(&_mutex);}T pop(){pthread_mutex_lock(&_mutex);if (_que.size() == 0){pthread_cond_wait(&_ccond, &_mutex);}T tmp = _que.front();_que.pop();//if (_que.size() < _lowwater)pthread_cond_signal(&_pcond);pthread_mutex_unlock(&_mutex);return tmp;}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_pcond);pthread_cond_destroy(&_ccond);}private:std::queue<T> _que;int _maxcap;   //最大容量pthread_mutex_t _mutex;pthread_cond_t _pcond;pthread_cond_t _ccond;int _lowwater;int _highwater; //控制水位线
};

阻塞队列设置为了模板,不只可以放入内置类型,也可以是自定义类型。弄一个任务类,有两个操作数,操作符加减乘除随机。一个变量记录结果,一个记录可靠性,如果有除0错误设置为对应值。提供返回string类型整个表达式的内容功能

#pragma once
#include <stdio.h>
#include <string>enum
{DIVZERO = 1,UNKNOW
};
std::string g_op = "+-*/";
struct task
{
public:task(int a, int b, char op):_a(a), _b(b), _op(op), _result(0), _exitcode(0){}void run(){switch(_op){case '+':_result = _a + _b;break;case '-':_result = _a - _b;break;case '*':_result = _a * _b;break;case '/':if (_b == 0){_exitcode = DIVZERO;}else{_result = _a / _b;}      break;default:_exitcode = UNKNOW;break;}//printf("%d+%d结果:%d\n", _a, _b, _a + _b);}std::string getresult(){std::string str = std::to_string(_a) + _op  + std::to_string(_b);str += "=";str += std::to_string(_result);str += " [exit:";str += std::to_string(_exitcode);str += "]";return str;}std::string gettask(){std::string str = std::to_string(_a) + _op  + std::to_string(_b);return str;}private:int _a;int _b;char _op;int _result;int _exitcode;
};

main文件生成两个线程,生产和消费,传入阻塞队列的实例,生产出一个任务,消费者完成

#include <unistd.h>
#include <cstdlib>
#include <iostream>
#include <ctime>
#include "blockqueue.hpp"
#include "task.hpp"void *produce(void *bk)
{BlockQueue<task>* block = static_cast<BlockQueue<task>*>(bk);while (true){int x1 = rand() % 10;usleep(10);int x2 = rand() % 10 + 1;char op = g_op[rand() % 4];task t(x1, x2, op);//生产printf("生产任务:%s\n", t.gettask().c_str());block->push(t);sleep(1);}
}void* consume(void* bk)
{BlockQueue<task>* block = static_cast<BlockQueue<task>*>(bk);while (true){//消费task n = block->pop();n.run();printf("完成任务:%s\n", n.getresult().c_str());sleep(1);}
}int main()
{srand(time(NULL));pthread_t ptid, ctid;BlockQueue<task>* block = new BlockQueue<task>();pthread_create(&ptid, nullptr, produce, block);pthread_create(&ctid, nullptr, consume, block);while (true){sleep(1);}delete block;return 0;
}

结果:
在这里插入图片描述

伪唤醒

当队列里只剩一个位置的时候,生产者如果不小心唤醒了多个生产者。这时它们都会去竞争锁,拿到锁的线程去生产然后放入,接着释放锁。正常情况下,应该消费线程拿到这个锁取数据,但因为刚刚唤醒了多个线程,可能会抢到锁继续放入数据,这时就会超出最大容量出现错误。所以要将if处改为循环,释放锁后判断如果满了就调条件变量里休眠

多生产多消费

将上面的单生成单消费改为多生产多消费版本

#include <unistd.h>
#include <cstdlib>
#include <iostream>
#include <ctime>
#include "blockqueue.hpp"
#include "task.hpp"void *produce(void *bk)
{BlockQueue<task>* block = static_cast<BlockQueue<task>*>(bk);while (true){int x1 = rand() % 10;usleep(10);int x2 = rand() % 10 + 1;char op = g_op[rand() % 4];task t(x1, x2, op);//生产printf("%p生产任务:%s\n", pthread_self(), t.gettask().c_str());block->push(t);sleep(1);}
}void* consume(void* bk)
{BlockQueue<task>* block = static_cast<BlockQueue<task>*>(bk);while (true){//消费task n = block->pop();n.run();printf("%p完成任务:%s\n", pthread_self(), n.getresult().c_str());//sleep(1);}
}int main()
{srand(time(NULL));BlockQueue<task>* block = new BlockQueue<task>();pthread_t ptid[3], ctid[5];for (int i = 0; i < 3; i++){pthread_create(&ptid[i], nullptr, produce, block);}for (int i = 0; i < 5; i++){pthread_create(&ctid[i], nullptr, consume, block);}for (int i = 0; i < 3; i++){pthread_join(ptid[i], nullptr);}for (int i = 0; i < 5; i++){pthread_join(ctid[i], nullptr);} delete block;return 0;
}

优势

虽然同一时间只能有一个执行流访问阻塞队列,多个生产者也只能有一个访问队列,那多个生产者和消费者的优势体现在什么地方。
生产者的数据从用户网络等地方获得,数据的获取也需要时间,当一个生产者往队列里放入数据时,其他生产者可以同时获取数据,后面只需要放入数据即可。消费者方拿到数据后要对数据加工处理,这部分也是需要花费时间,同样一个线程获取数据时,其他的可能正在处理获得的数据。所以说,这个模型提高了效率,并发程度,是高效的。

相关文章:

40 生产者消费者模型

生产者消费者模型 概念 为何要使用生产者消费者模型&#xff0c;这个是用过一个容器解决生产者和消费的强耦合问题。生产者和消费者之间不需要通讯&#xff0c;通过阻塞队列通讯&#xff0c;所以生产者生产完数据之后不用等待消费者处理&#xff0c;直接扔给阻塞队列&#xf…...

QT5之windowswidget_菜单栏+工具栏_核心控件_浮动窗口_模态对话框_标准对话框/文本对话框

菜单栏工具栏 新建工程基类是QMainWindow 1、 2、 3、 点.pro文件&#xff0c;添加配置 因为之后用到lambda&#xff1b; 在.pro文件添加配置c11 CONFIG c11 #不能加分号 添加头文件 #include <QMenuBar>//菜单栏的头文件 主窗口代码mainwindow.cpp文件 #include &q…...

Satellite, Aerial, and Underwater Communication Track(WCSP2023)

1.Dispersion Curve Extraction and Source Localization for Single Hydrophone by Combining Image Skeleton Extraction with Advanced Time-Frequency Analysis(图像骨架提取与先进时频分析相结合的单水听器色散曲线提取和源定位) 摘要&#xff1a;时频分析&#xff08;TF…...

AtCoder Regular Contest 176(ARC176)A、B

题目&#xff1a;AtCoder Regular Contest 176 - tasks 官方题解&#xff1a;AtCoder Regular Contest 176 - editorial 参考&#xff1a;atcoder regular 176 (ARC176) A、B题解 A - 01 Matrix Again 题意 给一个nn的方格&#xff0c;给出m个坐标(x,y)m&#xff0c;在方格中…...

VTK —— 二、教程六 - 为模型加入3D微件(按下i键隐藏或显示)(附完整源码)

代码效果 本代码编译运行均在如下链接文章生成的库执行成功&#xff0c;若无VTK库则请先参考如下链接编译vtk源码&#xff1a; VTK —— 一、Windows10下编译VTK源码&#xff0c;并用Vs2017代码测试&#xff08;附编译流程、附编译好的库、vtk测试源码&#xff09; 教程描述 本…...

一种基于图搜索的全局/局部路径避障策略

系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 TODO:写完再整理 文章目录 系列文章目录前言一种基于图搜索的全局/局部路径避障策略前言 认知有限,望大家多多包涵,有什么问题也希望能够与大家多交流,共同成长! 本文先对一种基于图搜索…...

LT2611UX四端口 LVDS转 HDMI2.0,带音频

描述LT2611UX 是一款面向机顶盒、DVD 应用的高性能 LVDS 至 HDMI2.0 转换器。LVDS输入可配置为单端口、双端口或四端口&#xff0c;具有1个高速时钟通道和3~4个高速数据通道&#xff0c;工作速率最高为1.2Gbps/通道&#xff0c;可支持高达19.2Gbps的总带宽。LT2611UX 支持灵活的…...

TypeError报错处理

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、Python中的TypeError简介 这个错误通常表示在方法调用时&#xff0c;参数类型不正确&#xff0c;或者在对字符串进行格式化操作时&#xff0c;提供的变量与预期不符。 二、错误的源头&#xff1a;字符串格式化…...

PHP的数组练习实验

实 验 目 的 掌握索引和关联数组&#xff0c;以及下标和元素概念&#xff1b; 掌握数组创建、初始化&#xff0c;以及元素添加、删除、修改操作&#xff1b; 掌握foreach作用、语法、执行过程和使用&#xff1b; 能应用数组输出表格和数据。 任务1&#xff1a;使用一维索引数…...

P3743 小鸟的设备

原题链接&#xff1a;小鸟的设备 - 洛谷 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 浮点数二分答案。 首先特判&#xff0c;如果接通设备每秒生成的能量p大于等于所有设备每秒消耗的能量&#xff08;a[1]a[2]..a[n]&#xff09;直接输出-1&…...

数字旅游以科技创新为动力:推动旅游服务的智能化、网络化和个性化发展,满足游客日益增长的多元化、个性化需求

目录 一、引言 二、科技创新推动旅游服务智能化发展 1、智能化技术的引入与应用 2、智能化提升旅游服务效率与质量 三、科技创新推动旅游服务网络化发展 1、网络化平台的构建与运营 2、网络化拓宽旅游服务渠道与范围 四、科技创新推动旅游服务个性化发展 1、个性化需求…...

64位的IP地址设想

现有的IP地址 IPv4有32位&#xff0c;不够用了。 IPv6有128位&#xff0c;相当多。 实际上&#xff0c;23385亿&#xff0c;只要在IPv4的基础上&#xff0c;加1比特就够用了&#xff0c;IPv6有些太长了。 64位的IP地址 这是个设想。 64位分成七段&#xff0c;8888881664&…...

1.python爬虫爬取视频网站的视频可下载的源url

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、爬取的源网站二、实现代码总结 一、爬取的源网站 http://www.lzizy9.com/ 在这里以电影片栏下的动作片为例来爬取。 可以看到视频有多页&#xff0c;因此需要…...

Linux目录结构

目录结构必背 &#xff01;&#xff01;&#xff01;&#xff01;&#xff08;在生产环境中必须知道自己在哪个目录下&#xff09;...

电脑问题2【彻底删除CompatTelRunner】

彻底删除CompatTelRunner 电脑偶尔会运行CompatTelRunner造成CPU占用的资源非常大,所以这里要想办法彻底关闭他 本文摘录于&#xff1a;https://mwell.tech/archives/539只是做学习备份之用&#xff0c;绝无抄袭之意&#xff0c;有疑惑请联系本人&#xff01; 解决办法是进入W…...

【算法】【贪心算法】【leetcode】870. 优势洗牌

题目地址&#xff1a;https://leetcode.cn/problems/advantage-shuffle/description/ 题目描述&#xff1a; 给定两个长度相等的数组 nums1 和 nums2&#xff0c;nums1 相对于 nums2 的优势可以用满足 nums1[i] > nums2[i] 的索引 i 的数目来描述。 返回 nums1 的任意排列&…...

Unity AVProVideo安卓播放视频问题

打包ARM64,插件里arm64里的几个库都设置arm64,平台选择安卓 Unity VideoPlayer使用url方式,Android平台下无法播放http链接的视频 主要原因:默认情况下,不允许从Android 8开始使用不安全的HTTP,并且必须使用HTTPS,除非分配了自定义的明文安全策略 解决办法: 只需要修…...

Redis使用手册之字符串

《Redis使用手册字符串设置》 目录 **《Redis使用手册字符串设置》**** SET&#xff1a;为字符串键设置值**** GETSET&#xff1a;获取旧值并设置新值**** MSET&#xff1a;一次为多个字符串键设置值**MGET&#xff1a;一次获取多个字符串键的值**** MSETNX&#xff1a;只在键不…...

嵌入式Linux学习第二天

今天学习linuxC编程。首先要熟悉linux下编写c程序的过程。 编写程序Hello World! 首先创建存放程序的文件夹&#xff0c;如下图所示&#xff1a; 接下来在创建一个文件夹来保存这节要编写的代码。指令&#xff1a;mkdir 3.1 接下来我们要设置VIM编辑器的一些配置&#xff0…...

【intro】图卷积神经网络(GCN)

本文为Graph Neural Networks(GNN)学习笔记-CSDN博客后续&#xff0c;内容为GCN论文阅读&#xff0c;相关博客阅读&#xff0c;kaggle上相关的数据集/文章/代码的阅读三部分&#xff0c;考虑到本人是GNN新手&#xff0c;会先从相关博客开始&#xff0c;进一步看kaggle&#xff…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

工业安全零事故的智能守护者:一体化AI智能安防平台

前言&#xff1a; 通过AI视觉技术&#xff0c;为船厂提供全面的安全监控解决方案&#xff0c;涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面&#xff0c;能够实现对应负责人反馈机制&#xff0c;并最终实现数据的统计报表。提升船厂…...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

计算机基础知识解析:从应用到架构的全面拆解

目录 前言 1、 计算机的应用领域&#xff1a;无处不在的数字助手 2、 计算机的进化史&#xff1a;从算盘到量子计算 3、计算机的分类&#xff1a;不止 “台式机和笔记本” 4、计算机的组件&#xff1a;硬件与软件的协同 4.1 硬件&#xff1a;五大核心部件 4.2 软件&#…...