生产者消费者模型
目录
一、生产者消费者模型的概念
二、生产者消费者模型的特点
三、生产者消费者模型优点
四、基于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索引--实例
学生表:Student (Sno, Sname, Ssex , Sage, Sdept) 学号,姓名,性别,年龄,所在系 Sno为主键 课程表:Course (Cno, Cname,) 课程号,课程名 Cno为主键 学生选课表:SC (Sno, Cno, Score)…...

浅聊一下,可中断锁(ReentrantLock)
前言 今天早上上厕所,上的我痔疮犯了,屁股一坐下去就感觉一根针在刺我,得的是外痔,之前还坚持用痔疮膏来着,但是感觉涂药的那个姿势以及位置我实在无法忍受,就把它给断了,到头来还是屁股糟了罪&…...
关于Arcgis林业数据处理的62个常用技巧
一、计算面积 ( 可以帮我们计算小班面积 ) 添加 AREA 字段,然后右键点击字段列,然后点击 CALCULATE VALUES; ---> 选择 ADVANCED --》把下面的代码输入,然后在最下面 处写 OUTPUT 点击 OK 就 OK 了。 Dim Outp…...

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

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 ,计算在 s 的子序列中 t 出现的个数。 字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新…...

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

Python编程 动态爱心
作者简介:一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页 目录 前言 一.所用库 1.random简介 2.math 简介 3.tkinter库的简介 二.实际图 三.…...
JavaScript :基础语法
位置: HTML 中的 Javascript 脚本代码必须位于 <script> 与 </script> 标签之间。 JavaScript 输出方式 window.alert() 弹出警告框。document.write() 将内容写到 HTML 文档中。innerHTML 写入到 HTML 元素。console.log() 写入到浏览器的控制台。 …...
buu [AFCTF2018]Single 1
题目描述: 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使用 原子操作,不可分割的操作,要么完整,要么不完整。 #includ…...

C语言指针剖析(初阶) 最详细!
什么是指针?指针和指针类型野指针指针运算指针和数组二级指针指针数组什么是指针?指针是内存中一个最小单元的编号,也就是地址。1.把内存划分为一个个的内存单元,一个内存单元的大小是一个字节。2.每个字节都给定唯一的编号&#…...

AcWing语法基础课笔记 第三章 C++中的循环结构
第三章 C中的循环结构 学习编程语言语法是次要的,思维是主要的。如何把头脑中的想法变成简洁的代码,至关重要。 ——闫学灿 学习循环语句只需要抓住一点——代码执行顺序! while循环 可以简单理解为循环版的if语句。If语句是判断一次…...
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…...
简约精美电商小程序【源码好优多】
简介 一款开源的电商系统,包含微信小程序和H5端,为大中小企业提供移动电子商务优秀的解决方案。 后台采用Thinkphp5.1框架开发,执行效率、扩展性、稳定性值得信赖。并且Jshop小程序商城上手难度低,可大量节省定制化开发周期。 功…...

全网详解 .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. 文章引言 今天在某低代码平台开发项目时,看到如下编译配置…...
从0开始学python -31
Python3 模块-1 在前面的几个章节中我们基本上是用 python 解释器来编程,如果你从 Python 解释器退出再进入,那么你定义的所有的方法和变量就都消失了。 为此 Python 提供了一个办法,把这些定义存放在文件中,为一些脚本或者交互…...

Jenkins的使用教程
介绍: Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能。 目的: 最重要目的就是把原来分散在各个机器上繁杂的工作全部…...
1.Maven的坐标和依赖
【maven坐标】1.groupId: 通常与域名反向一一对应2.artifactId: 通常使用实际项目名称3.version: 项目当前版本号4.packaging:maven项目的打包方式,默认是jar5.classifier: 定义构建输出的一些附属构件,例如:nexus-indexer-2.0.0.…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 
大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
离线语音识别方案分析
随着人工智能技术的不断发展,语音识别技术也得到了广泛的应用,从智能家居到车载系统,语音识别正在改变我们与设备的交互方式。尤其是离线语音识别,由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力,广…...

《Docker》架构
文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器,docker,镜像,k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...