015 Linux_生产消费模型

​🌈个人主页:Fan_558
🔥 系列专栏:Linux
🌹关注我💪🏻带你学更多操作系统知识
在这里插入图片描述

文章目录

  • 前言
    • 一、生产消费模型
      • (1)概念引入
      • (2)生产消费模型的优点
      • (3)生产消费模型的特点
    • 二、基于阻塞队列的生产消费模型
    • 三、基于环形队列的生产消费模型
      • (1)环形队列的生产消费模型特点
  • 小结

前言

本文将会向你介绍基于阻塞队列和环形队列的生产消费模型

一、生产消费模型

(1)概念引入

1、什么是生产者消费者模型?
生产者和消费是操作系统中一种重要的模型,它描述的是一种等待和通知的机制
2、本文定义:

生产者: 产生数据的模块,就形象地称为生产者;
消费者: 而处理数据的模块,就称为消费者;
缓冲区: 生产者和消费者之间的中介就叫做缓冲区。

什么是阻塞队列

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

在这里插入图片描述

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。为什么要使用生产者消费者模型,归根结底来说,生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。

(2)生产消费模型的优点

优点1、解决了强耦合问题
假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。
就好比说,你需要买零食,你还要给生产厂家打电话,然后他如果没有的话,还要制作,然后在交给你,这多麻烦呀,万一厂家哪天电话一变(相当于生产者线程的代码变化导致消费者的代码也要变化)那么你还得问问电话是多少。

优点2、支持并发(concurrency)即生产者和消费者可以是两个独立的并发主体,互不干扰的运行。
生产者直接调用消费者的某个方法,还有另一个弊端。由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者就会白白糟蹋大好时光。
使用了生产者/消费者模式之后,生产者和消费者可以是两个独立的并发主体(常见并发类型有进程和线程两种)。生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据。基本上不用依赖消费者的处理速度。

优点3、支持忙闲不均
如果制造数据的速度时快,时慢,缓冲区可以对其进行适当缓冲。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。

(3)生产消费模型的特点

  • 首先,生产者只需要关心“仓库”,并不需要关心具体的消费者。
  • 对于消费者而言,它不需要关心具体的生产者,它只需要关心这个“仓库”中还有没有东西存在。
  • 生产者生产的时候消费者不能进行“消费”,消费者消费的时候生产者不能生产,相当于一种互斥关系,
    即生产者和消费者一次只能有一人能访问到“仓库”。
  • “仓库”为空时不能进行消费。
  • “仓库”满时不能进行生产。
    在这里插入图片描述
    综上所述:可以记忆三二一原则:
    即三种关系、两个角色、一个场所
    1、三种关系:
  • 生产者与生产者(互斥)
    举个例子:生产商作为生产者的身份,生产者需要获得利益的最大化,当然是希望自己一家独大,那么生产者和生产者之间就是互斥的,最起码在1号生产商正在生产时,2号不能来捣乱,体现在代码中就是互斥
  • 生产者与消费者(同步与互斥)
    互斥:举个例子:比如当前你正要使用一号间厕所,此时清洁人员来了,那么他就需要在外面等待,同理他正在清理一号间,你也不能进入一号间
    同步:举个例子:如果你当前正在使用一号间厕所,那么清洁人员是可以清洁二号间的
  • 消费者与消费者(互斥)
    举个例子:两个人不能同时使用一间厕所

2、两个角色:

  • 生产者
  • 消费者

3、一个场所:

  • 缓冲区

基于以上特点,我们来编写代码吧

二、基于阻塞队列的生产消费模型

//Task.hpp#pragma once
#include <iostream>
#include <string>
std::string oper_="+-*/%";
enum{DivZero = 1,ModZero = 2,UnKnown
};class Task
{
public:Task(int x, int y, char op):data1_(x),data2_(y),oper_(op),result_(0),exitcode_(0){}//计算
void run()
{switch(oper_){case '+':result_ = data1_ + data2_;break;case '-':result_ = data1_ - data2_;break;case '*':result_ = data1_ * data2_;break;case '/':{if(data2_ == 0){exitcode_ = DivZero;}else result_ = data1_ / data2_;}break;case '%':if(data2_ == 0){exitcode_ = ModZero;}else result_ = data1_ % data2_;break;default:exitcode_ = UnKnown;break;}
}
void operator()()
{run();
}
//获取结果信息
std::string GetResult()
{std::string r = std::to_string(data1_);r += ' ';r += oper_;r += ' ';r += std::to_string(data2_);r += " = ";r += std::to_string(result_);r += " [code:";r += std::to_string(exitcode_);r += "]";return r;
}
//获取任务信息
std::string GetTask()
{std::string r = std::to_string(data1_);r += ' ';r += oper_;r += ' ';r += std::to_string(data2_);r += " =?";return r;
}
private:int data1_;int data2_;char oper_;int result_;int exitcode_;
};
//BlockQueue.hpp#include "Task.hpp"
#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
template <class T>
class BlockQueue
{//初始值static const int defalutnum = 10;
public:BlockQueue(int maxcap = defalutnum):maxcap_(maxcap){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&c_cond_, nullptr);pthread_cond_init(&p_cond_, nullptr);//定义上下限,满足下限就唤醒生产者生产,满足上限就唤醒消费者消费low_water_  = maxcap_ / 3;high_water_ = (maxcap_*2) / 3;}//消费const T &pop(){pthread_mutex_lock(&mutex_);//条件变量while(q_.size() == 0){//进入等待队列中排队pthread_cond_wait(&c_cond_, &mutex_);}T out = q_.front();q_.pop();if(q_.size() < low_water_){pthread_cond_signal(&p_cond_);}//唤醒消费者pthread_mutex_unlock(&mutex_);return out;}//生产void push(const T &in){pthread_mutex_lock(&mutex_);//条件变量while(q_.size() == maxcap_){//进入等待队列中排队pthread_cond_wait(&p_cond_, &mutex_);}//生产一个数据q_.push(in);//唤醒消费者if(q_.size() > high_water_){pthread_cond_signal(&c_cond_);}pthread_mutex_unlock(&mutex_);}//析构~BlockQueue(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&c_cond_);pthread_cond_destroy(&p_cond_);}private:std::queue<T> q_;   //共享资源(阻塞队列)int maxcap_;    //最大容量pthread_mutex_t mutex_; //锁pthread_cond_t p_cond_;   //生产者条件变量pthread_cond_t c_cond_;   //消费者条件变量int low_water_;int high_water_;
};

值得一提的是条件变量的判断部分我们不能使用if,而是要用while,原因是防止产生伪唤醒的现象,消费者只消费了一个,但是唤醒时,(如果我们使用broadcast去唤醒生产者线程),它们就不会在条件变量下等待了,而是都去竞争这个锁了。如果此时一个生产者线程竞争成功后就去生产(假设只剩下一个空位供生产),然后它生产后,紧接着唤醒消费者线程去消费,然后解锁,但是此时还有另外两个生产者线程在竞争锁,他们都在条件变量下等待,然后有可能又是生产者线程拿到锁(消费者却没有竞争到锁),继续向后执行,但是呢,if条件不会再判断了,函数返回继续向下执行,结果就是生产多了,已经没有空间去生产了,这就是伪唤醒状态

总而言之,while循环会让我们对(上述背景中等待的)生产者们再次判断是否满足生产的条件,防止产生伪唤醒的现象
在这里插入图片描述

//main.cc#include "BlockQueue.hpp"
#include "Task.hpp"
#include <stdlib.h>
//生产
void* Productor(void* args)
{BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);//sleep(5);while(true){int number1 = rand() % 10 + 1;usleep(10);//让随机数number2出现0的情况int number2 = rand() % 10;int r = rand() % 5;Task t(number1, number2, oper_[r]);bq->push(t);     //传入任务信息std::cout << "thread id: "<< pthread_self() <<" "<< "传入任务:" << t.GetTask() <<std::endl;sleep(1);}
}
//消费
void* Consumer(void* args)
{BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);while(true){Task ret = bq->pop();ret();std::cout << "thread id: "<< pthread_self() <<" "<< "run...:" << ret.GetResult() <<std::endl;sleep(1);}
}int main()
{//创建指向阻塞队列的指针(其中队列中放着一个个Task对象(任务))BlockQueue<Task> *bq = new BlockQueue<Task>();pthread_t c[3], p[5];//创建生产、消费线程for(int i = 0; i < 5; i++){pthread_create(p + i, nullptr, Productor, bq);}for(int i = 0; i < 3; i++){pthread_create(c + i, nullptr, Consumer, bq);}//线程等待for(int i = 0; i < 5; i++){pthread_join(p[i], nullptr);}for(int i = 0; i < 3; i++){pthread_join(c[i], nullptr);}delete bq;return 0;
}

运行结果
在这里插入图片描述

三、基于环形队列的生产消费模型

(1)环形队列的生产消费模型特点

环形队列采用数组模拟,用模运算来模拟环状特性。
环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态。但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程
在这里插入图片描述

初始化信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:pshared:0表示线程间共享,非零表示进程间共享value:信号量初始值销毁信号量
int sem_destroy(sem_t *sem);等待信号量
功能:等待信号量,使信号量-1,若信号量为负数,那么线程就会阻塞等待。
int sem_wait(sem_t *sem); //P()发布信号量
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。若信号量没达到最大值,则说明有现成正在阻塞等待,V操作就会唤醒一个线程。
int sem_post(sem_t *sem);//V()

于是我们就可以定义两个信号量
N表示环形队列(缓冲区)中能存放的空间数量
默认:DataSem初始生产数据值为0

注意:
在这里插入图片描述

在这里插入图片描述

代码如下(省略Task.hpp

//ringQueue#pragma once
#include <iostream>
#include <vector>
#include <semaphore.h>const static int defaultcap = 5;template<class T>
class RingQueue{
private://申请信号量void P(sem_t &sem){sem_wait(&sem);}//释放信号量void V(sem_t &sem){sem_post(&sem);}void Lock(pthread_mutex_t &mutex){pthread_mutex_lock(&mutex);}void Unlock(pthread_mutex_t &mutex){pthread_mutex_unlock(&mutex);}
public:RingQueue(int cap = defaultcap):ringqueue_(cap), cap_(cap), c_step_(0), p_step_(0){sem_init(&cdata_sem_, 0, 0);sem_init(&pspace_sem_, 0, cap);pthread_mutex_init(&c_mutex_, nullptr);pthread_mutex_init(&p_mutex_, nullptr);}//生产void Push(const T &in) {//判断: 申请空间信号量(失败阻塞)P(pspace_sem_);//加锁Lock(p_mutex_);ringqueue_[p_step_] = in;p_step_++;p_step_ %= cap_;Unlock(p_mutex_);//释放数据信号量V(cdata_sem_);}//消费void Pop(T *out)       {//判断: 申请数据信号量(失败阻塞)P(cdata_sem_);//加锁Lock(c_mutex_);*out = ringqueue_[c_step_];c_step_++;c_step_ %= cap_;Unlock(c_mutex_);//释放空间信号量V(pspace_sem_);}~RingQueue(){sem_destroy(&cdata_sem_);sem_destroy(&pspace_sem_);pthread_mutex_destroy(&c_mutex_);pthread_mutex_destroy(&p_mutex_);}
private:std::vector<T> ringqueue_;int cap_;int c_step_;       // 消费者下标int p_step_;       // 生产者下标sem_t cdata_sem_;  // 消费者关注的数据资源sem_t pspace_sem_; // 生产者关注的空间资源pthread_mutex_t c_mutex_;pthread_mutex_t p_mutex_;
};
//main.cc#include "ringQueue.hpp"
#include "Task.hpp"
#include <stdlib.h>
#include <unistd.h>
#include <string>struct ThreadData
{RingQueue<Task> *rq;        //指向环形队列的指针std::string threadname;     //线程名
};//生产
void* Productor(void* args)
{ThreadData *td = static_cast<ThreadData *>(args);RingQueue<Task> *rq = td->rq;//sleep(5);while(true){int number1 = rand() % 10 + 1;usleep(10);//让随机数number2出现0的情况int number2 = rand() % 10;int r = rand() % 5;Task t(number1, number2, oper_[r]);rq->Push(t);     //传入任务信息std::cout <<"我是:" << td->threadname << " " <<"任务信息: " << t.GetTask() << std::endl;sleep(1);}return nullptr;
}
//消费
void* Consumer(void* args)
{ThreadData *td = static_cast<ThreadData *>(args);RingQueue<Task> *rq = td->rq;Task t;while(true){rq->Pop(&t);t();std::cout <<"我是:" << td->threadname <<" "<< " result: " << t.GetResult() << std::endl;sleep(1);}return nullptr;
}int main()
{//创建指向阻塞队列的指针(其中队列中放着一个个Task对象(任务))srand(time(nullptr) ^ getpid());RingQueue<Task> *rq = new RingQueue<Task>(50);pthread_t c[3], p[5];for (int i = 0; i < 5; i++){ThreadData *td = new ThreadData();td->rq = rq;td->threadname = "Productor-" + std::to_string(i);pthread_create(p + i, nullptr, Productor, td);}for (int i = 0; i < 3; i++){ThreadData *td = new ThreadData();td->rq = rq;td->threadname = "Consumer-" + std::to_string(i);pthread_create(c + i, nullptr, Consumer, td);}for (int i = 0; i < 5; i++){pthread_join(p[i], nullptr);}for (int i = 0; i < 3; i++){pthread_join(c[i], nullptr);}delete rq;return 0;
}

运行结果
在这里插入图片描述

小结

今日的分享就到这里啦,如果本文存在疏漏或错误的地方,还请您能够指出!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/755012.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

键牌 6寸水口钳工业级电子斜嘴水口剪偏口钳子电工专用小斜口钳

品牌&#xff1a;键牌 型号&#xff1a;6寸水口钳灰红 材质&#xff1a;不锈钢 颜色分类&#xff1a;6寸水口钳灰红 多用途电工钳&#xff0c;高硬度&#xff0c;韧性好&#xff0c;材质优。 匠心之作&#xff0c;精工典范&#xff0c;不锈钢材质&#xff0c;加厚刀刃&am…

【JavaWeb】Spring非阻塞通信 - Spring Reactive之WebFlux的使用

【JavaWeb】Spring非阻塞通信 - Spring Reactive之WebFlux的使用 文章目录 【JavaWeb】Spring非阻塞通信 - Spring Reactive之WebFlux的使用参考资料一、初识WebFlux1、什么是函数式编程1&#xff09;面向对象编程思维 VS 函数式编程思维&#xff08;封装、继承和多态描述事物间…

Magical Combat VFX 2

我们为Unity推出的最新资产包:魔法战斗VFX包!这个包非常适合为你的游戏添加激烈而致命的魔法。有30多种独特的效果,包括血液、酸和毒咒,你可以在战斗场景中大显身手。而且移动支持和优化是首要任务,你可以在旅途中使用这些效果,而不用担心性能问题。使用功能齐全、移动就…

windows11安装SQL server数据库报错等待数据库引擎恢复句柄失败(二)

windows11安装SQL server数据库报错等待数据库引擎恢复句柄失败&#xff08;二&#xff09;&#xff0c;昨天在给网友远程的时候发现了一个新的问题。 计算机系统同样是Windows11&#xff0c;通过命令查出来的扇区相关结果也都是4096&#xff0c;但是最后的安装还是提示SQL ser…

JVM内存模型深度解读

JVM&#xff08;Java Virtual Machine&#xff0c;Java虚拟机&#xff09;对于Java开发者和运行 Java 应用程序而言至关重要。其重要性主要体现在跨平台性、内存管理和垃圾回收、性能优化、安全性和稳定性、故障排查与性能调优等方面。今天就下学习一下 JVM 的内存模型。 一、…

嵌入式学习40-数据结构

数据结构 1.定义 一组用来保存一种或者多种特定关系的 数据的集合&#xff08;组织和存储数据&#xff09; 程序的设计&#xff1a; …

31-Java前端控制器模式(Front Controller Pattern)

Java前端控制器模式 实现范例 前端控制器模式&#xff08;Front Controller Pattern&#xff09;是用来提供一个集中的请求处理机制&#xff0c;所有的请求都将由一个单一的处理程序处理该处理程序可以做认证/授权/记录日志&#xff0c;或者跟踪请求&#xff0c;然后把请求传给…

使用RabbitMQ,关键点总结

文章目录 1.MQ的基本概念2.常见的MQ产品3.MQ 的优势和劣势3.1 优势3.2 劣势 4.RabbitMQ简介4.1RabbitMQ 中的相关概念 1.MQ的基本概念 MQ全称 Message Queue&#xff08;消息队列&#xff09;&#xff0c;是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。…

掌握FL Studio21的编曲功能,让你的音乐创作更上一层楼

音乐是人类最美好的语言&#xff0c;它能够跨越国界、文化和语言&#xff0c;将人们紧密地联系在一起。在当今数字化时代&#xff0c;音乐创作已经不再是专业人士的专利&#xff0c;越来越多的音乐爱好者开始尝试自己动手制作音乐。而FL Studio21中文版编曲软件正是这样一个为你…

pdf文件属性的删除

pdf文件属性的删除 投标过程中需要处理文件属性&#xff0c;特别是word文件属性以及pdf文件的处理 这里讲解pdf文件属性的处理 word处理在我的另外一个博客中&#xff0c;word文件属性的处理 https://ht666666.blog.csdn.net/article/details/134102504 一般用 adobe acroba…

数字人解决方案——ER-NeRF实时对话数字人论文解读

简介 本文提出了一种新的基于条件神经辐射场&#xff08;Condition NeRF&#xff09;的talking portrait合成框架ER-NeRF&#xff0c;能够在较小的参数量下实现高精度的实时渲染和快速收敛。该方法利用空间区域的不平等贡献来指导谈话肖像建模&#xff0c;以提高动态头部重建的…

MNN 围炉札记

文章目录 一、MNN 资料二、使用示例三、源码分析1、createFromFile、createFromBuffer1.1 Content1.2 createFromBufferInternal1.3 Net1.4 Interpreter1.5 Interpreter::Interpreter 2、createRuntime2.1 RuntimeInfo2.2 Schedule::getApprociateType2.2.1 MNNGetExtraRuntime…

《论文阅读》EmpDG:多分辨率交互式移情对话生成 COLING 2020

《论文阅读》EmpDG:多分辨率交互式移情对话生成 COLING 2020 前言简介模型架构共情生成器交互鉴别器损失函数前言 亲身阅读感受分享,细节画图解释,再也不用担心看不懂论文啦~ 无抄袭,无复制,纯手工敲击键盘~ 今天为大家带来的是《EmpDG: Multi-resolution Interactive E…

Linux 文件系统:文件描述符、管理文件

目录 一、三个标注输入输出流 二、文件描述符fd 1、通过C语言管理文件—理解文件描述符fd 2、文件描述符实现原理 3、文件描述符0、1、2 4、总结 三、如何管理文件 1、打开文件的过程 2、内核空间的结构 struct task_struct&#xff08;PCB&#xff09; struct file…

你真的了解SpringBoot注解?阿里巴巴面试告诉你答案!

如有疑问或者更多的技术分享,欢迎关注我的微信公众号“知其然亦知其所以然”! 大家好,我是小米!今天我来和大家分享一下在阿里巴巴面试中常见的一个问题:SpringBoot注解。SpringBoot作为当今流行的Java开发框架,注解是其灵魂所在,熟练掌握这些注解对于应对面试非常有帮…

腾讯云服务器入站规则端口开放使用指南(CentOS系统)

第一步&#xff1a;开放安全组入站规则 来源处0.0.0.0/0是对IPv4开发&#xff0c;::/0是对IPv6开放&#xff1b; 协议端口按照提示填写即可。云服务器防火墙开放 第三步&#xff1a;本地防火墙开放 sudo firewall-cmd --zonepublic --add-port你的端口号/tcp --perma…

【c++】类和对象

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;c_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.面向过程和面向对象初步认识 面向过程&#xff08;C语言&#xff09; 面向对象&#xff08;C&#xff09; 2.类的引入和定义 2.1 类…

FPGA——DDR3的IP核

FPGA——DDR3的ip核 IP核配置基于MIG核代码基于AXI接口的DDR3 IP核配置 1 2 3 4 5 6 基于MIG核代码 控制MIG核的信号进行读写 module MIG_APP_Drive(input i_ui_clk ,input i_ui_rst ,input init_calib_…

Legacy|电脑Windows系统如何迁移到新安装的硬盘?系统迁移详细教程!

前言 前面讲了很多很多关于安装系统、重装系统的教程。但唯独没有讲到电脑换了新的硬盘之后&#xff0c;怎么把旧系统迁移到新的硬盘上。 今天小白就来跟各位小伙伴详细唠唠&#xff1a; 开始之前需要把系统迁移的条件准备好&#xff0c;意思就是在WinPE系统下&#xff0c;可…

AI赋能:加速MWORKS.Sysplorer仿真设计的新途径

近年来&#xff0c;人工智能技术的应用逐渐兴起&#xff0c;为仿真领域带来了新的机遇。随着现代工程系统的复杂性日益增加&#xff0c;MWORKS.Sysplorer作为一种广泛应用于系统仿真和设计的工具&#xff0c;面临着模型日益庞大和计算资源有限的挑战。为了解决这一问题&#xf…