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

生产者消费者模型

目录

一、生产者消费者模型的概念

二、生产者消费者模型的特点

三、生产者消费者模型优点

四、基于BlockingQueue的生产者消费者模型

4.1 基本认识

4.2 模拟实现


一、生产者消费者模型的概念

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题

生产者和消费者彼此之间不直接通讯,而通过容器来通讯,所以生产者生产完数据之后不用等待消费者处理,直接将生产的数据放到这个容器当中;消费者也不用找生产者索要数据,而是直接从这个容器中取数据。容器就类似于一个缓冲区,平衡了生产者和消费者的处理能力,这个容器完成了生产者和消费者之间的解耦

二、生产者消费者模型的特点

  • 三种关系: 生产者和生产者(互斥关系)、消费者和消费者(互斥关系)、生产者和消费者(互斥关系、同步关系)
  • 两种角色: 生产者和消费者(通常由进程或线程承担)
  • 一个交易场所: 通常指的是内存中的一段缓冲区(可以自己通过某种方式组织)

生产者和生产者、消费者和消费者、生产者和消费者,它们之间为什么会存在互斥关系?

介于生产者和消费者之间的容器可能会被多个执行流同时访问,因此需要将该临界资源用互斥锁保护起来。所以所有生产者和消费者都会竞争式的申请锁,因此生产者和生产者、消费者和消费者、生产者和消费者之间都存在互斥关系

生产者和消费者之间为什么会存在同步关系?

若一直让生产者生产,那么当生产者生产的数据装满容器后,生产者再生产数据就会生产失败。
反之,让消费者一直消费,那么当容器当中的数据被消费完后,消费者再进行消费就会消费失败。
虽然这样不会造成任何数据不一致的问题,但是这样会引起另一方的饥饿问题,是非常低效的。应该让生产者和消费者访问该容器时具有一定的顺序性,比如让生产者先生产,然后再让消费者进行消费。

注意: 互斥关系保证的是数据的正确性,而同步关系是为了让多线程之间协同起来

三、生产者消费者模型优点

  • 解耦
  • 支持并发,提高效率
  • 支持忙闲不均

若在主函数中调用某一函数,那么必须等该函数体执行完后才继续执行主函数的后续代码,因此函数调用本质上是一种紧耦合。对应到生产者消费者模型中,函数传参实际上就是生产者生产的过程,而执行函数体实际上就是消费者消费的过程,但生产者只负责生产数据,消费者只负责消费数据,在消费者消费期间生产者可以同时进行生产,因此生产者消费者模型本质是一种松耦合

四、基于BlockingQueue的生产者消费者模型

4.1 基本认识

在多线程编程中,阻塞队列(Blocking Queue)是一种常用于实现生产者消费者模型的数据结构

其与普通的队列的区别在于:

  • 当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中放入了元素
  • 当队列满时,往队列里存放元素的操作会被阻塞,直到有元素从队列中取出

阻塞队列最经典的应用场景:管道

4.2 模拟实现

下面以单生产者、单消费者为例进行讲解与实现

#include <iostream>
#include <queue>
#include <pthread.h>template <class T>
class BlockQueue
{
public:BlockQueue(size_t capacity = 4) : _capacity(capacity){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_full,nullptr);pthread_cond_init(&_empty,nullptr);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_full);pthread_cond_destroy(&_empty);}void push(const T& data){pthread_mutex_lock(&_mutex);while (IsFull()) {//不能进行生产,直到阻塞队列可以容纳新的数据pthread_cond_wait(&_full, &_mutex);}_queue.push(data);std::cout << "Producer: " << data << std::endl;pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_empty); //唤醒在empty条件变量下等待的消费者线程}void pop(T& data){pthread_mutex_lock(&_mutex);while (IsEmpty()) {//不能进行消费,直到阻塞队列有新的数据pthread_cond_wait(&_empty, &_mutex);}data = _queue.front();_queue.pop();std::cout << "Consumer: " << data << std::endl;pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_full); //唤醒在full条件变量下等待的生产者线程}private:bool IsFull() { return _queue.size() == _capacity; }bool IsEmpty() { return _queue.empty(); }private:std::queue<T> _queue;size_t _capacity;pthread_mutex_t _mutex;pthread_cond_t _full;pthread_cond_t _empty;
};

判断是否满足生产消费条件时不能用if,而应该用while:

pthread_cond_wait函数有可能调用失败,调用失败后该执行流就会继续往后执行。为了避免出现上述情况,就要让线程被唤醒后再次进行判断,确认是否真的满足生产消费条件,因此这里必须要用while进行判断

生产者消费者步调一致

#include <unistd.h>
#include "BlockQueue.hpp"void* Producer(void* arg)
{BlockQueue<int>* bq = (BlockQueue<int>*)arg;while (true) { //生产者不断进行生产sleep(1);int data = rand() % 100 + 1;bq->push(data);}
}
void* Consumer(void* arg)
{int data = 0;BlockQueue<int>* bq = (BlockQueue<int>*)arg;while (true) { //消费者不断进行消费sleep(1);bq->pop(data);}
}int main() 
{pthread_t producer,consumer;BlockQueue<int>* bq = new BlockQueue<int>;pthread_create(&producer,nullptr,Producer,(void*)bq);pthread_create(&consumer,nullptr,Consumer,(void*)bq);pthread_join(producer,nullptr);pthread_join(consumer,nullptr);delete bq;return 0;
}

由于代码中生产者是每隔一秒生产一个数据,而消费者是每隔一秒消费一个数据,因此运行代码后我们可以看到生产者和消费者的执行步调是一致的 

生产者速度快,消费者速度慢

void* Producer(void* arg)
{BlockQueue<int>* bq = (BlockQueue<int>*)arg;while (true) { //生产者不断进行生产int data = rand() % 100 + 1;bq->push(data);}
}
void* Consumer(void* arg)
{int data = 0;BlockQueue<int>* bq = (BlockQueue<int>*)arg;while (true) { //消费者不断进行消费sleep(1);bq->pop(data);}
}

此时由于生产者生产的很快,运行代码后一瞬间生产者就将阻塞队列装满。此时生产者想要再进行生产就只能在full条件变量下进行等待,直到消费者消费完一个数据后,生产者才会被唤醒进而继续进行生产,生产者生产完一个数据后又会进行等待,因此后续生产者和消费者的步调又变成一致的了

生产者速度慢,消费者速度快

void* Producer(void* arg)
{BlockQueue<int>* bq = (BlockQueue<int>*)arg;while (true) { //生产者不断进行生产sleep(1);int data = rand() % 100 + 1;bq->push(data);}
}
void* Consumer(void* arg)
{int data = 0;BlockQueue<int>* bq = (BlockQueue<int>*)arg;while (true) { //消费者不断进行消费bq->pop(data);}
}

虽然消费者消费的快,但开始时阻塞队列中是没有数据的,因此消费者只能在empty条件变量下等待,直到生产者生产完一个数据后,消费者才会被唤醒进而进行消费,消费者消费完这一个数据后又会进行等待,因此生产者和消费者的步调就是一致的

设置唤醒策略

可以设置一些策略。譬如,下面当阻塞队列当中存储的数据大于队列容量的一半时,再唤醒消费者线程进行消费;当阻塞队列当中存储的数据小于队列容器的一半时,再唤醒生产者线程进行生产

void push(const T &data)
{pthread_mutex_lock(&_mutex);while (IsFull()) { // 不能进行生产,直到阻塞队列可以容纳新的数据pthread_cond_wait(&_full, &_mutex);}_queue.push(data);std::cout << "Producer: " << data << std::endl;if (_queue.size() >= _capacity / 2) {pthread_cond_signal(&_empty); // 唤醒在empty条件变量下等待的消费者线程}pthread_mutex_unlock(&_mutex);
}
void pop(T &data)
{pthread_mutex_lock(&_mutex);while (IsEmpty()) { // 不能进行消费,直到阻塞队列有新的数据pthread_cond_wait(&_empty, &_mutex);}data = _queue.front();_queue.pop();std::cout << "Consumer: " << data << std::endl;if (_queue.size() <= _capacity / 2) {pthread_cond_signal(&_full); // 唤醒在full条件变量下等待的生产者线程}pthread_mutex_unlock(&_mutex);
}

仍然让生产者生产快,消费者消费慢。运行代码后生产者还是一瞬间将阻塞队列装满后进行等待,但此时不是消费者消费一个数据就唤醒生产者线程,而是当阻塞队列当中的数据小于等于队列容器的一半时,才会唤醒生产者线程进行生产

基于任务的生产者消费者模型

实际使用生产者消费者模型时可不是简单的让生产者生产一个数字让消费者进行打印而已,前面的代码只是为了理解生产者消费者模型而已。
编写BlockingQueue时当中存储的数据就进行了模板化,那么就可以让BlockingQueue当中存储其他类型的数据。

譬如编写一个Task类(其中包含需要执行的任务),BlockingQueue中就存储Task对象。此时生产者放入阻塞队列的数据就是Task对象,而消费者从阻塞队列拿到Task对象后,就可以用该对象调用Run成员函数进行数据处理。

总之,根据需要进行编写即可

相关文章:

生产者消费者模型

目录 一、生产者消费者模型的概念 二、生产者消费者模型的特点 三、生产者消费者模型优点 四、基于BlockingQueue的生产者消费者模型 4.1 基本认识 4.2 模拟实现 一、生产者消费者模型的概念 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题 生产者和…...

mysql索引--实例

学生表&#xff1a;Student (Sno, Sname, Ssex , Sage, Sdept) 学号&#xff0c;姓名&#xff0c;性别&#xff0c;年龄&#xff0c;所在系 Sno为主键 课程表&#xff1a;Course (Cno, Cname,) 课程号&#xff0c;课程名 Cno为主键 学生选课表&#xff1a;SC (Sno, Cno, Score)…...

浅聊一下,可中断锁(ReentrantLock)

前言 今天早上上厕所&#xff0c;上的我痔疮犯了&#xff0c;屁股一坐下去就感觉一根针在刺我&#xff0c;得的是外痔&#xff0c;之前还坚持用痔疮膏来着&#xff0c;但是感觉涂药的那个姿势以及位置我实在无法忍受&#xff0c;就把它给断了&#xff0c;到头来还是屁股糟了罪&…...

关于Arcgis林业数据处理的62个常用技巧

一、计算面积 ( 可以帮我们计算小班面积 ) 添加 AREA 字段&#xff0c;然后右键点击字段列&#xff0c;然后点击 CALCULATE VALUES; ---> 选择 ADVANCED &#xff0d;&#xff0d;》把下面的代码输入&#xff0c;然后在最下面 处写 OUTPUT 点击 OK 就 OK 了。 Dim Outp…...

一些NLP术语

一些NLP术语pre-training&#xff08;预训练&#xff09;fine-tuning&#xff08;微调&#xff09;下游任务Few-shot Learning&#xff08;少样本学习&#xff09;Prompt&#xff1f;&#xff08;自然语言提示信息&#xff09;二级标题三级标题pre-training&#xff08;预训练&…...

Session详解,学习 Session对象一篇文章就够了

目录 1 Session概述 2 Session原理 3 Session使用 3.1 获取Session 3.2 Session保存数据 3.3 Session获取数据 3.4 Session移除数据 4 Session与Request应用区别 4.1 Session和request存储数据 4.2 获取session和request中的值 4.3 session和request区别效果 5 Sess…...

Java——不同的子序列

题目链接 leetcode在线oj题——不同的子序列 题目描述 给定一个字符串 s 和一个字符串 t &#xff0c;计算在 s 的子序列中 t 出现的个数。 字符串的一个 子序列 是指&#xff0c;通过删除一些&#xff08;也可以不删除&#xff09;字符且不干扰剩余字符相对位置所组成的新…...

Git 基本操作之Git GUI界面和git命令行如何选择

1. 为啥推荐使用git命令行 我发现公司有很多的同事都喜欢使用git的GUI界面工具&#xff0c;喜欢鼠标点点点就完成了代码的提交&#xff0c;这种方式的确是比较简单便捷&#xff0c;但是却存在风险。先上一个事故给大家醒醒脑。 VScode Git 界面操作引发的惨案 上面的惨案是VS…...

Python编程 动态爱心

作者简介&#xff1a;一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.所用库 1.random简介 2.math 简介 3.tkinter库的简介 二.实际图 三.…...

JavaScript :基础语法

位置&#xff1a; HTML 中的 Javascript 脚本代码必须位于 <script> 与 </script> 标签之间。 JavaScript 输出方式 window.alert() 弹出警告框。document.write() 将内容写到 HTML 文档中。innerHTML 写入到 HTML 元素。console.log() 写入到浏览器的控制台。 …...

buu [AFCTF2018]Single 1

题目描述&#xff1a; Jmqrida rva Lfmz (JRL) eu m uqajemf seny xl enlxdomrexn uajiderc jxoqarerexnu. Rvada mda rvdaa jxooxn rcqau xl JRLu: Paxqmdyc, Mrrmjs-Yalanja mny oekay. Paxqmdyc-urcfa JRLu vmu m jxiqfa xl giaurexnu (rmusu) en dmnza xl jmrazxdeau. Lxd …...

Linux C++ 200行完成线程池类

文章目录1、atomic使用2、volatile关键字3、条件变量4、成员函数指针使用5、线程池6、主线程先退出对子线程影响7、return、exit、pthread_exit区别8、进程和线程的区别1、atomic使用 原子操作&#xff0c;不可分割的操作&#xff0c;要么完整&#xff0c;要么不完整。 #includ…...

C语言指针剖析(初阶) 最详细!

什么是指针&#xff1f;指针和指针类型野指针指针运算指针和数组二级指针指针数组什么是指针&#xff1f;指针是内存中一个最小单元的编号&#xff0c;也就是地址。1.把内存划分为一个个的内存单元&#xff0c;一个内存单元的大小是一个字节。2.每个字节都给定唯一的编号&#…...

AcWing语法基础课笔记 第三章 C++中的循环结构

第三章 C中的循环结构 学习编程语言语法是次要的&#xff0c;思维是主要的。如何把头脑中的想法变成简洁的代码&#xff0c;至关重要。 ——闫学灿 学习循环语句只需要抓住一点——代码执行顺序&#xff01; while循环 可以简单理解为循环版的if语句。If语句是判断一次&#xf…...

A simple freeD tracking protocol implementation written in golang

可以使用的go版本freed调试代码 可以通过udp发送和接收数据 What is freeD? freeD is a very simple protocol used to exchange camera tracking data. It was originally developed by Vinten and is now supported by a wide range of hard- and software including Unreal…...

简约精美电商小程序【源码好优多】

简介 一款开源的电商系统&#xff0c;包含微信小程序和H5端&#xff0c;为大中小企业提供移动电子商务优秀的解决方案。 后台采用Thinkphp5.1框架开发&#xff0c;执行效率、扩展性、稳定性值得信赖。并且Jshop小程序商城上手难度低&#xff0c;可大量节省定制化开发周期。 功…...

全网详解 .npmrc 配置文件:比如.npmrc的优先级、命令行,如何配置.npmrc以及npm常用命令等

文章目录1. 文章引言2. 简述.npmrc3. 配置.npmrc3.1 .npmrc配置文件的优先级3.2 .npmrc设置的命令行3.3 如何设置.npmrc4. 配置发布组件5. npm常用命令6. 重要备注6.1 yarn6.2 scope命名空间6.3 镜像出错1. 文章引言 今天在某低代码平台开发项目时&#xff0c;看到如下编译配置…...

从0开始学python -31

Python3 模块-1 在前面的几个章节中我们基本上是用 python 解释器来编程&#xff0c;如果你从 Python 解释器退出再进入&#xff0c;那么你定义的所有的方法和变量就都消失了。 为此 Python 提供了一个办法&#xff0c;把这些定义存放在文件中&#xff0c;为一些脚本或者交互…...

Jenkins的使用教程

介绍&#xff1a; Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件的持续集成变成可能。 目的: 最重要目的就是把原来分散在各个机器上繁杂的工作全部…...

1.Maven的坐标和依赖

【maven坐标】1.groupId: 通常与域名反向一一对应2.artifactId: 通常使用实际项目名称3.version: 项目当前版本号4.packaging&#xff1a;maven项目的打包方式&#xff0c;默认是jar5.classifier: 定义构建输出的一些附属构件&#xff0c;例如&#xff1a;nexus-indexer-2.0.0.…...

STEP3-VL-10B性能评测:10B参数模型在A100上吞吐量达18.7 token/s实测

STEP3-VL-10B性能评测&#xff1a;10B参数模型在A100上吞吐量达18.7 token/s实测 最近&#xff0c;阶跃星辰开源了一个让我眼前一亮的模型——STEP3-VL-10B。作为一个10B参数级别的多模态视觉语言模型&#xff0c;它的表现确实让人惊喜。我在A100上实测后发现&#xff0c;它的…...

探索燃料电池PEMFC非等温两相流模型:流道液态水膜态水的奥秘

燃料电池PEMFC非等温两相流模型&#xff0c;考虑流道液态水膜态水。在燃料电池的世界里&#xff0c;PEMFC&#xff08;质子交换膜燃料电池&#xff09;因其高效、清洁等诸多优点&#xff0c;成为了科研与工业应用领域的热门话题。今天咱就来深挖一下PEMFC中的非等温两相流模型&…...

毕设代码二手房数据实战:从爬取到可视化的一站式工程实现

最近在帮学弟学妹看计算机专业的毕业设计&#xff0c;发现“二手房数据分析”真是个热门选题。想法都挺好&#xff0c;但一到动手实现&#xff0c;很多人就卡在了数据上&#xff1a;要么网站反爬太严数据抓不下来&#xff0c;要么抓下来的数据乱七八糟没法用&#xff0c;好不容…...

《Linux 是怎样工作的》第 2 章:用户模式实现的功能

一、先建立核心认知&#xff1a;两个世界的边界 计算机系统被严格划分为两个隔离的运行环境&#xff0c;这是保障系统安全与稳定的基础&#xff1a; 内核态&#xff08;Kernel Mode&#xff09;&#xff1a;相当于「小区物业」&#xff0c;唯一能直接操作 CPU、内存、硬盘、网…...

xbee_lib嵌入式通信库架构与工程实践指南

1. XBee通信库&#xff08;xbee_lib&#xff09;深度解析与嵌入式工程实践XBee系列模块作为Digi公司推出的成熟Zigbee/802.15.4/Point-to-Multipoint无线通信解决方案&#xff0c;广泛应用于工业物联网、远程传感器网络、智能农业及楼宇自动化等场景。xbee_lib是一个面向嵌入式…...

**发散创新:用Go语言构建高性能服务网格代理——从零实现Sidecar模式**在微服务架构日益普及的今天,

发散创新&#xff1a;用Go语言构建高性能服务网格代理——从零实现Sidecar模式 在微服务架构日益普及的今天&#xff0c;服务网格&#xff08;Service Mesh&#xff09; 已成为保障流量治理、安全认证与可观测性的核心基础设施。传统基于API网关的集中式控制方式已难以满足动态…...

实战指南:如何用Mask R-CNN在iSAID数据集上提升航空影像分割效果(附调参技巧)

航空影像实例分割实战&#xff1a;Mask R-CNN在iSAID数据集上的调优策略 航空影像分析正逐渐成为城市规划、灾害监测和国防安全等领域的关键技术。与常规自然图像不同&#xff0c;这类影像通常包含大量密集分布的小目标&#xff0c;且目标尺度变化极大——从几个像素的小型车辆…...

JSMN嵌入式JSON解析器:零拷贝、无内存分配的轻量实现

1. JSMN&#xff1a;面向嵌入式系统的极简JSON解析器深度解析 1.1 设计哲学与工程定位 JSMN&#xff08;JSON Parser for Microcontrollers&#xff09;并非通用JSON库的轻量裁剪版&#xff0c;而是在资源受限场景下重新定义“解析”边界的产物。其核心设计信条是&#xff1a;…...

AI辅助配置:让快马平台智能生成最优化的openclaw安装与调试方案

AI辅助配置&#xff1a;让快马平台智能生成最优化的openclaw安装与调试方案 最近在做一个机器人抓取项目&#xff0c;需要安装和配置openclaw模块。作为一个刚接触机器人控制的新手&#xff0c;面对复杂的依赖关系和参数调试&#xff0c;一开始真是有点无从下手。好在发现了In…...

OpenClaw新手入门:Qwen3.5-9B镜像一键部署与基础配置

OpenClaw新手入门&#xff1a;Qwen3.5-9B镜像一键部署与基础配置 1. 为什么选择Qwen3.5-9B作为OpenClaw的"大脑"&#xff1f; 去年冬天&#xff0c;当我第一次尝试用OpenClaw自动化处理周报时&#xff0c;发现默认的小模型经常把"会议纪要"理解成"会…...