生产消费者模型

news2024/11/16 13:35:14

生产消费者模型概念

        生产消费者模型实际上就是通过一个容器,将生产者和消费者之间的强耦合问题解决掉。

        没有使用生产者消费者模型时,生产者和消费者之间直接相互联通,两者之间强耦合,若是一方更换,那另一方也需要随之更换,那样是十分不可取的。

        而使用了生产者消费者模型就不会出现这样的情况。

         比如使用了阻塞队列使用的生产消费模型,在生产者和消费者之间有一个阻塞队列的介入,二者并不直接连接,若是一方更换也不会影响到另一方,大大减少了代码的改动。

321原则

        生产者和消费者之间一个原则——“321原则”。

        一般的生产消费模型都遵循该原则。

  • 三种关系:生产者之间互斥,消费者之间互斥,生产者和消费者之间互斥和同步关系
  • 两种角色:生产者线程和消费者线程
  • 一个交易场所:一个特定结构的缓冲区

生产消费模型的特点

        生产消费模型一般有两种特点

  1. 解耦合
  2. 削峰填谷

        解耦合问题在开头已经说过,那么削峰填谷是怎么一回事呢?

        在一般的工作场景中,有时入口服务器会出现请求暴涨的情况,但是由于入口服务器的请求计算量不大,即便计算量暴涨也不会导致服务器挂掉,但是作为响应服务器的一方的计算量很大,在没有生产消费者模型的情况下,会导致响应服务器崩溃

       而若是有生产消费模型作为中间容器,当入口服务器出现很多请求,阻塞队列会存储所有请求,而响应服务器则不需要一瞬间全部响应,而是维持原来的速度处理服务器,从而防止服务器崩溃。——削峰

        像这种一瞬间出现大量请求的情况不会持续很长,等峰值过去,响应服务器不会因此减缓速度,而是按原来速度处理队列任务。——填谷

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

        在这里就介绍基于阻塞队列的生产消费模型。

        正如名字所说,该模型以阻塞队列作为容器来实现该模型。

        而实现一个阻塞队列就需要队列以及两个用来阻塞的条件变量,我们先直接看代码。

#include<queue>
#include<iostream>
#include<pthread.h>
#include<unistd.h>

using namespace std;

static int Max = 10;

template<class T>
class BlockingQueue{
public:
    BlockingQueue(int MaxCap = Max)
        :_MaxCap(MaxCap)
    {
        pthread_mutex_init(&mutex,NULL);
        pthread_cond_init(&pcond,NULL);
        pthread_cond_init(&ccond,NULL);
    }

    void Push(const T& data)
    {
        pthread_mutex_lock(&mutex);
        //若是满了
        if(is_Full())
        {   
            pthread_cond_wait(&pcond,&mutex);
        }
        //此时确定有一个空位
        q.push(data);
        //此时确定有一个资源
        pthread_cond_signal(&ccond);
        pthread_mutex_unlock(&mutex);

    }

    void Pop(T* data)
    {
        pthread_mutex_lock(&mutex);
        if(is_Empty())
        {
            pthread_cond_wait(&ccond,&mutex);
        }
        //此时确定有一个资源
        *data = q.front();
        q.pop();
        pthread_cond_signal(&pcond);
        pthread_mutex_unlock(&mutex);
    }

    ~BlockingQueue()
    {
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&pcond);
        pthread_cond_destroy(&ccond);
    }
private:
    bool is_Full()
    {
        return q.size() == _MaxCap;
    }

    bool is_Empty()
    {
        return q.empty();
    }
    queue<T> q;
    pthread_mutex_t mutex;
    pthread_cond_t pcond;//生产者
    pthread_cond_t ccond;//消费者
    int _MaxCap;
};

        一个用于多线程情况下的模型,当然需要具备同步和互斥的功能,而且为了实现阻塞的功能,我们需要有条件变量的存在,并且由于该模型是由生产者线程和消费者线程互斥且同步的访问队列这个临界资源,且二者看待资源的角度是不一样的,所以需要两个条件变量。

生产者的角度

        从生产者的角度来看,生产者所需要的资源是队列的剩余空间

        若是队列中的空间已满,那么生产者应该停下来阻塞式的等待消费者消费,直到队列中出现新的空位。

        因而在Push函数中,我们需要先判断队列是否已满,再来决定是否放入数据。

         假设我们判断队列已满,那么生产者就会在wait函数这里阻塞式的等待

        而若是队列未满,那么生产者就会向队列放入数据,并且唤醒等待的消费者线程——因为我们默认队列没数据——那么消费者线程一开始就会阻塞式等待唤醒,并且解锁。

消费者的角度

        从消费者角度来说,消费者所需要的是队列中的资源

        若是队列中为空,那么消费者就应该阻塞式的等待生产者生产数据,直到队列中出现新的资源。

        因而在Pop函数中,我们需要先判断队列是否为空,再来决定是否取出数据。

         假设我们判断队列已空,那么消费者就会在wait函数这里阻塞式的等待

        而若是队列未空,那么消费者就会从队列取出数据,并且唤醒等待的生产者线程——因为有可能队列已满,生产者会等待消费者消费,直到被消费者唤醒。

        为了方便理解,此处我们使用单消费者单生产者的情况来理解。

#include <pthread.h>
#include <iostream>
#include <unistd.h>

#include <stdlib.h>
#include "BlockingQueue.hpp"

void *pstart_routine(void *args)
{
    BlockingQueue<int> *bq = static_cast<BlockingQueue<int> *>(args);
    while (true)
    {
        int data = rand() % 1000;
        bq->Push(data);
        cout<<"Pthread has push one data ! data : "<<data<<endl;
        sleep(1);
    }
}

void *cstart_routine(void *args)
{
    BlockingQueue<int> *bq = static_cast<BlockingQueue<int> *>(args);
    while (true)
    {
        int data ;
        bq->Pop(&data);
        cout<<"Cthread has pop one data ! data : "<<data<<endl;
        sleep(1);
    }
}

int main()
{
    srand((unsigned int)time(NULL));
    BlockingQueue<int> *bq = new BlockingQueue<int>;
    pthread_t pt, ct;
    pthread_create(&pt, NULL, pstart_routine, bq);
    pthread_create(&ct, NULL, cstart_routine, bq);

    pthread_join(pt, NULL);
    pthread_join(ct, NULL);

    return 0;
}

         能看到我们确实是生产者刚生产,消费者才回去消费。

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

        在阻塞队列的生产消费模型中,有一个漏洞。

        当我们的生产者或者消费者访问阻塞队列时,它会将该队列给保护住,其他生产者或者消费者就无法访问了,然而访问的资源也许并不是全部,而是一部分资源,这样就会导致效率低下。

        为了解决该问题,又出现了基于环形队列的生产消费模型

        基于环形队列的生产消费模型是利用信号量来实现的。

        我们都知道信号量的原理就是一个计数器,每个执行流在访问临界资源之前都应该访问该计数器,失败就阻塞,直到成功。

信号量的PV操作

  • P操作:使信号量-1,若信号量为负数,那么线程就会阻塞等待。
  • V操作:使信号量+1,若信号量为负数,则说明有现成正在阻塞等待,V操作就会唤醒一个线程。

        信号量的PV操作也是原子的,因而不用上锁。

        回归正题,基于环形队列的生产消费模型是利用数组下标取模和数组模拟一个环形队列进行的,其阻塞的功能是由信号量的PV操作进行的。

        接下来我们直接看看代码。

#include <iostream>
#include <pthread.h>
#include <semaphore.h>
#include <vector>
#include<assert.h>
using namespace std;

template <class T>
class RingQueue
{
private:
    void P(sem_t &sem)
    {
        int n = sem_wait(&sem); // 表示该资源已经被占用了
        assert(n == 0);
        (void )n;
    }

    void V(sem_t &sem)
    {
      int n =  sem_post(&sem); // 表示该资源已经用完了.
      assert(n == 0);
        (void )n;
    }

public:
    RingQueue(int cap = 10)
        : _cap(cap), q(cap)
    {
        sem_init(&space, 0, _cap); // 默认刚开始没有资源
        sem_init(&datas, 0, 0);
        pstep = cstep = 0;
    }

    void Push(const T &data)
    {
        P(space);
        q[pstep++] = data;
        pstep %= _cap;
        V(datas);
    }

    void Pop(T *data)
    {
        P(datas);
        *data = q[cstep++];
        cstep %= _cap;
        V(space);
    }
    ~RingQueue()
    {
        sem_destroy(&space);
        sem_destroy(&datas);
    }

private:
    vector<T> q;
    sem_t space; // 有多少空间
    sem_t datas; // 有多少任务
    int pstep;
    int cstep;
    int _cap;
};

        该模型的生产者所需要的资源是队列中的剩余空间。由于最开始没有资源,因此我们设置space信号量为队列大小。   

        而消费者所看重的资源是队列中的剩余资源,最开始没有资源,因此设置datas信号量为0;   

        此外,由于此处的环形队列是用数组模拟的,因此需要使用两个整数分别记录存放数据的位置和取出数据的位置

基于环形队列的生产消费模型的注意事项

  • 消费者不可超过生产者

消费者无法取出不存在的数据,而只在生产者前面存在数据,因此消费者不可超过生产者

  • 生产者不可超过消费者一圈 

当生产者超过消费者一圈时,若是还放入数据,就会覆盖未来得及被消费的数据。

  • 生产者和消费者不可同时访问同一位置

当生产者和消费者同时访问一个位置,就相当于同时访问了一块临界资源,这是错误的。

生产者和消费者的互相制约

         当我们看到Push函数和Pop函数时,我们发现Push一个数据前,需要对space进行P操作,而且Push一个数据后,还需要对datas进行V操作,相对的Pop也是进行着相反的操作。

        实际上这是生产者和消费者相互唤醒,防止堵死。

        接着我们看看实操,通过单生产者和单消费者来进行实验。

#include "RingQueue.hpp"
#include <stdlib.h>
#include <unistd.h>
void *pstart_routine(void *args)
{
    RingQueue<int> *rq = static_cast<RingQueue<int>*>(args);
    while (true)
    {
        int data = rand() % 100;
        rq->Push(data);
        cout << "P thread has push a data ! data : " << data << endl;
        sleep(1);
    }
}

void *cstart_routine(void *args)
{
    RingQueue<int> *rq = static_cast<RingQueue<int>*>(args);

    while (true)
    {
        int data;
        rq->Pop(&data);
        cout << "c thread has pop a data ! data : " << data << endl;
        sleep(1);
    }
}

int main()
{
    srand((unsigned int)time(NULL));
    pthread_t p, c;
    RingQueue<int> *rq = new RingQueue<int>();
    pthread_create(&p, NULL, pstart_routine, rq);
    pthread_create(&c, NULL, cstart_routine, rq);

    pthread_join(p, NULL);
    pthread_join(c, NULL);
    return 0;
}

        

         从代码中我们发现,肯定是先生产一个任务,再消费一个任务,这是因为模型内部信号量大小决定的。

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

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

相关文章

在 Jetpack Compose 中使用 ViewPager

简介 Jetpack Compose 是一个现代化的&#xff0c;声明式的 UI 工具包&#xff0c;让我们可以更方便地构建原生 Android UI。在本篇文章中&#xff0c;我们将会讨论如何在 Jetpack Compose 中使用 ViewPager。 什么是 ViewPager? ViewPager 是一个提供左右滑动切换视图的 U…

DynaSLAM代码详解(2) — Mask RCNN物体检测框架

目录 2.1 前言 2.2 Mask R-CNN优点 2.3 Mask R-CNN框架解析 (1) Mask R-CNN算法步骤 (2) Faster-R-CNN (3) FCN (4) ROIPooling和ROIAlign的分析与比较 (5) Mask R-CNN损失 参考链接&#xff1a; &#xff08;1&#xff09;Mask R-CNN网络详解_fcn太阳花的小绿豆_太…

Java开发专家阿里P6-P7面试题大全及答案汇总(持续更新)二十七、Ribbon和Feign的区别...

一、CPU100%问题如何快速定位 答案 1.执行top -c &#xff0c;显示进程运行信息列表 键入P (大写p)&#xff0c;进程按照CPU使用率排序 2.找到最耗CPU的线程 top -Hp 10765 &#xff0c;显示一个进程的线程运行信息列表 键入P (大写p)&#xff0c;线程按照CPU使用率排序 …

IDEA集成Maven

目录 配置Maven环境 创建Maven项目 Maven坐标 导入Maven项目 Maven依赖管理&#xff08;核心&#xff09; 配置Maven环境 两种方法 每没创建一个maven项目都需要在项目中配置一遍在所有设置中进行全局设置&#xff0c;适用于所有的maven项目 步骤 在idea的初始界面中找到所…

ASEMI整流桥2W10的结构特点和应用领域

编辑-Z 整流桥2W10是一种常用的电子元件&#xff0c;用于将交流电转换为直流电。本文将从工作原理、结构特点、应用领域和发展趋势四个方面对整流桥2W10进行详细阐述。 工作原理 整流桥2W10是由四个二极管组成的桥式整流电路。当输入的交流电信号通过整流桥时&#xff0c;根据…

文心大模型3.5完成内测

据报道&#xff0c;日前&#xff0c;百度文心大模型3.5版本已经完成内测应用&#xff0c;并在三大公开测试集上展现了出色的表现&#xff0c;其综合能力评测得分已经超过ChatGPT&#xff0c;部分中文能力甚至超越了GPT-4。 根据《中国科学报》的报道&#xff0c;3月份&#xf…

2023年全球零信任现状报告发布丨面临集成挑战,如何突破知易行难?

近日&#xff0c;专注网络与安全融合的全球网络安全领导者Fortinet&#xff08;NASDAQ&#xff1a;FTNT&#xff09;宣布发布《2023年全球零信任现状报告》及其调查发现。该报告揭示了零信任安全当前部署和实施现状&#xff0c;以及 IT 团队在应对后疫情时代的混合办公模式的安…

还找不到好用的UI设计工具,来看这篇

即时设计是一个基于云的在线协作工具&#xff0c;专门为国产设计团队打造。与其它在线协作工具相比&#xff0c;即时设计具有更强的项目团队合作功能&#xff0c;也更容易实现上手操作。它可以帮助企业或团队从0到1的创建、测试和交付的设计项目。在即时设计的帮助下&#xff0…

更快地分割任何事物:面向移动应用的轻量级Sam

文章目录 摘要1、简介2、相关工作3、适合移动设备的SAM3.1、背景和项目目标3.2、提出方法 4、实验4.1、实验设置4.2、MobileSAM的性能与原版SAM相当4.3、MobileSAM优于FastSAM 5、结论 摘要 https://arxiv.org/pdf/2306.14289v2.pdf 分割任何事物模型(SAM)因其令人印象深刻的零…

健身器材BS EN ISO 20957标准

健身器材出口欧洲需要符合CE认证的 BS EN ISO 20957标准&#xff0c;而且 BS EN ISO 20957规定了固定训练设备的安全要求和试验方法&#xff0c;也就是固定训练设备在使用过程中的一般安全要求。这包括用于体育协会&#xff0c;教育机构&#xff0c;酒店&#xff0c;体育馆&…

Lazada官方运营服务商分享;店铺没流量?优化好标题是关键

一.商品标题简介 Q:商品标题是什么&#xff1f; A:商家上传商品时&#xff0c;填写的标题&#xff0c;会展示在PDP页面上端 Q:为什么商品标题很重要&#xff1f; A:商品标题可根据买家搜索的关键字来将商品呈现在搜索结果中&#xff0c;因此&#xff0c;需要在商品标题中尽…

欧科云链联合大湾区警方共话区块链安全

为了应对日益严峻的区块链衍生犯罪&#xff0c;日前欧科云链联合粤港澳大湾区警务人员在深圳开展了为期一天的三地警务研讨会&#xff0c;期间获得包括香港商报、澳门日报、澳门法治报、澳门商报、香港雅虎财经等多家港澳权威媒体的报道&#xff0c;引发三地警务机关的高度关注…

ceph故障解决

今天一早发现ceph集群出错&#xff0c;根据报错&#xff0c;可以判断出是时间不同步和一个存储池没有起来导致的 一、解决时间同步 1.1检查时间同步的ntp服务是否启动&#xff08;发现有两台服务器的ntp服务关闭了&#xff09; systemctl status ntp1.2 重启ntp服务(重启服…

超详细AI二维码制作教程:手把手教你如何用Stable Diffusion 生成一个创意二维码?

AI已来&#xff0c;未来已来! 来势汹汹的人工智能&#xff0c;如同创世纪的洪水&#xff0c;正在全世界的范围内引发一场史无前例的科技革命。AI正在改变世界&#xff01;而我们正是这场巨变的见证者。 今天我们要介绍的内容就是&#xff1a;如何利用AI工具Stable Diffusion&a…

python接口自动化(二十六)--批量执行用例 discover(详解)

简介  我们在写用例的时候&#xff0c;单个脚本的用例好执行&#xff0c;那么多个脚本的时候&#xff0c;如何批量执行呢&#xff1f;这时候就需要用到 unittest 里面的 discover 方法来加载用例了。加载用例后&#xff0c;用 unittest 里面的 TextTestRunner 这里类的 run 方…

【Docker】Docker的部署含服务和应用、多租环境、Linux内核的详细介绍

前言 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。 &#x1f4d5;作者简介&#xff1a;热…

SSH 远程口令登录及免密登录

简介&#xff1a; SSH是一种网络协议,用于计算机之间的加密登录。如果一个用户从本地计算机使用SSH协议登录另一台计算机我们就可以认为这种登录时安全的&#xff0c;即使被中途截获,密码也不会泄露 安装 1.服务器安装OpenSSH(CentOS系统默认安装了openssh) 1.yum install op…

如何利用Java实现 AI 人脸融合特效

Java实现AI人脸融合特效 项目背景AI 人脸融合特效的原理代码实现第一步&#xff1a;调用token接口人脸融合部分工具类最终效果图 项目背景 最近自从chat-gpt爆火以来&#xff0c;AI技术在人工智能领域持续迭代的创新&#xff0c;为人们的生活带来了许多震撼的应用。比如其中的…

C++刷题第六天 454.四数相加II 383. 赎金信 15. 三数之和 18. 四数之和

454. 四数相加 II 哈希表的经典题目 给你四个整数数组 nums1、nums2、nums3 和 nums4 &#xff0c;数组长度都是 n &#xff0c;请你计算有多少个元组 (i, j, k, l) 能满足&#xff1a; 0 < i, j, k, l < n nums1[i] nums2[j] nums3[k] nums4[l] 0 解题思路 这个…

【QT】——QChartView,QChart,QValueAxis类的使用,折线图,柱状图,饼状图的实现

目录 1.QChartView——视图 2.QChart——图表 3.QValueAxis类方法——坐标轴类 4.QAbstractSeries 5.折线图的实现 6.柱状图的实现 7.饼状图的实现 QChart主要由一下几个大类组成&#xff1a;QChartView类、QChart类、QAbstractSeries类、QValueAxis类 QChartView类 为视…