【Linux】生产者消费者模型

目录

什么是生产消费者模型

为什么要使用生产消费者模型

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


什么是生产消费者模型

        生产者消费者模型是一种常见的并发编程模型,用于解决生产者和消费者之间数据交换和同步的问题。在这个模型中,生产者会生成数据并将其放入共享的缓冲区或队列中,而消费者则从缓冲区中取出数据进行处理。

        该模型图如下:

 321原则:

        上面的图中,我们可以把生产者 看成是 超市的供应商,仓库就是所谓的超市, 而消费者 就是顾客。

        它们之间存在3种关系

        生产者和生产者竞争,即互斥关系。 比如供应商间会竞争在超市中上架自己的产品。

        消费者和消费者竞争,即互斥关系。 因为超市有很多货物,所以这种现象不明显,但是假设商家 提供某个物品的促销活动,这个时候消费者便会抢,竞争性也就很明显了。

        生产者和消费者互斥/同步关系。要么生产者在超市放入,要么消费者在消费,两者必须互斥,不能一起,而且一定有顺序。

        2种角色: 生产者/消费者.

        1个交易场所:超市

后面的代码将按照这个321原则进行编写.


为什么要使用生产消费者模型

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

        

        并行处理和提高系统效率:生产者消费者模型能够并行处理数据,允许生产者和消费者以独立的方式执行。生产者可以持续地生成数据,而消费者则以自己的速度进行处理。这样可以提高系统的处理能力和效率,充分利用多核处理器和并发编程的潜力。


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

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

 这里再来说一个结论:

        进程间通信的前提是:让不同进程看到同一份资源。

        进程间通信的本质是:生产消费者模型(一方写入数据,一方接受数据)

所以我们之前提到的管道,内部也是一个简单的阻塞队列,有数据就接收,没数据就阻塞等待。

所以接下来我们 要自己实现一个基于阻塞队列的生产消费者模型


整体思路是:先设计一个BlockQueue类表示阻塞队列,然后里面一共包含五个成员变量:分别为

queue类型bq_代表队列,capacity_代表队列的容量,mtx_代表互斥锁,两个条件变量:Empty_代表当前阻塞队列是否为空,Full_代表阻塞队列是否已满

然后提供四个接口:

  • push():生产者生产数据需要将其push到阻塞队列中,首先要检测临界资源是否满足条件,满足条件后再访问临界资源,否则pthread_cond_wait将该线程等待挂起。当访问完成后,给另一个条件变量Empty_利用pthread_cond_signal发送信号。
  • pop():消费者每次消费数据需要将阻塞队列中的数据pop(),然后依然要检测临界资源是否满足条件,不满足pthread_cond_wait将线程等待挂起,满足条件后访问临界资源。然后给另一个条件变量Full_发送信号。
  • isQueueEmpty()&&isQueueFull():判断阻塞队列是否为空,或者阻塞队列是否已满

类设计完成后,在主函数中,创建两个线程,分别代表生产者和消费者,然后各自执行对应的回调函数,生产者不断push,而消费者每隔一秒pop一次。

        这样我们观察到的现象应该是:生产者很快将阻塞队列填满,然后便挂起等待,消费者每隔一秒取一次数据,然后给Full_发送信号唤醒生产者,然后生产者继续生产,此时满了之后又挂起等待,消费者隔一秒消费一次数据后,又将生产者唤醒...

        所以整体的代码如下:

  BlockQueue.hpp文件

#pragma once#include <iostream>
#include <queue>
#include <mutex>
#include <pthread.h>
using namespace std;
#define DEFAULT_CAP 5template <class T>
class BlockQueue
{
public:BlockQueue(int capacity = DEFAULT_CAP) : capacity_(capacity){pthread_mutex_init(&mtx_, nullptr);pthread_cond_init(&Empty_, nullptr);pthread_cond_init(&Full_, nullptr);}void push(const T &in) // 生产者{pthread_mutex_lock(&mtx_);// 1.先检测临界资源是否能够满足访问条件// break;//不要break,不然下次线程还可能继续进来,死循环// pthread_cond_wait:我们此时在临界区中,是持有锁的,如果去挂起等待了,那锁怎么办呢.// 消费者由于无法申请锁,也没办法拿出资源,所以它的第二个参数是一个锁,成功调用wait后,传入的锁会被自动释放// 当被唤醒时,线程会从哪里醒来呢:从哪里挂起,就从哪里唤醒// 当线程被唤醒的时候,pthread_cond_wait会自动帮线程获取锁。// pthread_cond_wait:只要是一个函数,就有可能能调用失败,所以调用失败时,会存在伪唤醒的情况,所以要把if换成whilewhile (isQueueFull()){pthread_cond_wait(&Full_, &mtx_);}// 2.访问临界资源bq_.push(in);//if (bq_.size() >= capacity_ / 2) //制定策略,可以自己随便设置pthread_cond_signal(&Empty_);pthread_mutex_unlock(&mtx_);}void pop(T *out){pthread_mutex_lock(&mtx_);while (isQueueEmpty())pthread_cond_wait(&Empty_, &mtx_);*out = bq_.front();bq_.pop();pthread_cond_signal(&Full_);pthread_mutex_unlock(&mtx_);}bool isQueueEmpty(){return bq_.size() == 0;}bool isQueueFull(){return bq_.size() == capacity_;}~BlockQueue(){pthread_mutex_destroy(&mtx_);pthread_cond_destroy(&Empty_);pthread_cond_destroy(&Full_);}private:queue<T> bq_;          // 阻塞队列int capacity_;         // 容量上线pthread_mutex_t mtx_;  // 通过互斥锁保证队列安全pthread_cond_t Empty_; // 表示把bq是否为空pthread_cond_t Full_;  // 表示bq是否满
};

ConProd.cc文件

#include "BlockQueue.hpp"
#include<unistd.h>void* consumer(void* args)
{BlockQueue<int>* bqueue = (BlockQueue<int>*)args;while(true){int a = 0;bqueue->pop(&a);cout << "消费了一个数据: " << a << endl;sleep(1);}return nullptr;
}void* producter(void* args)
{BlockQueue<int>* bqueue = (BlockQueue<int>*)args;int a = 1;while(true){  bqueue->push(a);cout << "生产了一个数据: " << a++ << endl;}return nullptr;
}int main()
{BlockQueue<int>* bqueue = new BlockQueue<int>();pthread_t c,p;pthread_create(&c,nullptr,consumer,bqueue);pthread_create(&p,nullptr,producter,bqueue);pthread_join(c,nullptr);pthread_join(p,nullptr);return 0;
}

然后我们运行这段程序,看看效果:


以上是单线程的,我们可以改成多线程的生产消费者模型,而且我们可以改成处理任务,而不是单纯的对一个变量进行修改,改成生产者进行产生加法式子,消费者计算结果并输出。

我们在主函数中,将内容改成如下:

int myAdd(int x , int y)
{sleep(1);return x + y;
}
void* consumer(void* args)
{BlockQueue<Task>* bqueue = (BlockQueue<Task>*)args;while(true){//获取任务Task t;bqueue->pop(&t);//完成任务cout << pthread_self() <<" consumer:" <<  t.x_ << "+" << t.y_ << " = "  << t() << endl;sleep(1);}return nullptr;
}void* producter(void* args)
{BlockQueue<Task>* bqueue = (BlockQueue<Task>*)args;//生产任务while(true){  //制作任务int x = rand() % 100 + 1;usleep(rand() % 1000);int y = rand() % 50 + 1;Task t(x,y,myAdd);//生产任务bqueue->push(t);//输出消息cout  << pthread_self() << " producter: " << t.x_ << "+" << t.y_ << "=?" << endl;sleep(1);}int main()
{srand((unsigned int) time(nullptr)^ getpid() ^ 1223);BlockQueue<Task>* bqueue = new BlockQueue<Task>();pthread_t c[2],p[2];for(int i = 0; i < 2; i++)pthread_create(c+i,nullptr,consumer,bqueue);for(int j = 0; j < 2; j++)pthread_create(p+j,nullptr,producter,bqueue);for(int i = 0; i < 2; i++)pthread_join(c[i],nullptr);for(int j = 0; j < 2; j++)pthread_join(p[j],nullptr);return 0;
}

 我们可以看到不同的生产者和消费者在不断的产生任务和消费处理任务。

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

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

相关文章

Spring之AOP的特性

一. AOP简介 AOP是Aspect-Oriented Programming的缩写&#xff0c;即面向切面编程。利用oop思想&#xff0c;可以很好的处理业务流程&#xff0c;但是不能把系统中某些特定的重复性行为封装到模块中。例如&#xff0c;在很多业务中都需要记录操作日志&#xff0c;结果我们不得…

HTML5 游戏开发实战 | 五子棋

01、五子棋游戏设计的思路 在下棋过程中&#xff0c;为了保存下过的棋子的信息&#xff0c;使用数组 chessData。chessData&#xff3b;x&#xff3d;&#xff3b;y&#xff3d;存储棋盘(x&#xff0c;y)处棋子信息&#xff0c;1 代表黑子&#xff0c;2 代表白子&#xff0c;0…

【100天精通python】Day38:GUI界面编程_PyQT从入门到实战(中)

目录 专栏导读 4 数据库操作 4.1 连接数据库 4.2 执行 SQL 查询和更新&#xff1a; 4.3 使用模型和视图显示数据 5 多线程编程 5.1 多线程编程的概念和优势 5.2 在 PyQt 中使用多线程 5.3 处理多线程间的同步和通信问题 5.3.1 信号槽机制 5.3.2 线程安全的数据访问 Q…

高效数据传输:轻松上手将Kafka实时数据接入CnosDB

本篇我们将主要介绍如何在 Ubuntu 22.04.2 LTS 环境下&#xff0c;实现一个KafkaTelegrafCnosDB 同步实时获取流数据并存储的方案。在本次操作中&#xff0c;CnosDB 版本是2.3.0&#xff0c;Kafka 版本是2.5.1&#xff0c;Telegraf 版本是1.27.1 随着越来越多的应用程序架构转…

Linux驱动开发之点亮三盏小灯

头文件 #ifndef __HEAD_H__ #define __HEAD_H__//LED1和LED3的硬件地址 #define PHY_LED1_MODER 0x50006000 #define PHY_LED1_ODR 0x50006014 #define PHY_LED1_RCC 0x50000A28 //LED2的硬件地址 #define PHY_LED2_MODER 0x50007000 #define PHY_LED2_ODR 0x50007014 #define…

TiDB基础介绍、应用场景及架构

1. 什么是newsql NewSQL 是对各种新的可扩展/高性能数据库的简称&#xff0c;这类数据库不仅具有NoSQL对海量数据的存储管理能力&#xff0c;还保持了传统数据库支持ACID和SQL等特性。 NewSQL是指这样一类新式的关系型数据库管理系统&#xff0c;针对OLTP&#xff08;读-写&…

移动通信系统的LMS自适应波束成形技术matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ..................................................................... idxx0; while idxx&…

docker 基础知识

目录 1. 加载docker镜像 2. 显示所有的镜像 3. 执行镜像&#xff0c;生成容器&#xff0c; 每执行一次&#xff0c;便生成一个容器 4. 显示出container名称 5. 进入容器 6. 如何将文件传入容器内 首先要确保已经安装了docker。注意&#xff1a;服务器上若没有管理员权限&am…

(贪心) 剑指 Offer 14- II. 剪绳子 II ——【Leetcode每日一题】

❓剑指 Offer 14- II. 剪绳子 II 难度&#xff1a;中等 给你一根长度为 n 的绳子&#xff0c;请把绳子剪成整数长度的 m 段&#xff08;m、n 都是整数&#xff0c;n > 1 并且 m>1 &#xff09;&#xff0c;每段绳子的长度记为 k[0],k[1]...k[m - 1] 。请问 k[0]*k[1]*.…

数据结构的图存储结构

目录 数据结构的图存储结构 图存储结构基本常识 弧头和弧尾 入度和出度 (V1,V2) 和 的区别,v2> 集合 VR 的含义 路径和回路 权和网的含义 图存储结构的分类 什么是连通图&#xff0c;&#xff08;强&#xff09;连通图详解 强连通图 什么是生成树&#xff0c;生…

小程序-基于vant的Picker组件实现省市区选择

一、原因 因vant/area-data部分的市/区数据跟后台使用的高德/腾讯省市区有所出入&#xff0c;故须保持跟后台用同一份数据&#xff0c;所以考虑以下几个组件 1、Area 2、Cascader 3、Picker 因为使用的是高德地图的省市区json文件&#xff0c;用area的话修改结构代价太大&…

解锁园区交通新模式:园区低速自动驾驶

在当今科技飞速发展的时代&#xff0c;自动驾驶技术成为了备受关注的领域之一。尤其是在园区内部交通管理方面&#xff0c;自动驾驶技术的应用正在日益受到重视。 园区低速自动驾驶的实现需要多个技术领域的协同合作&#xff0c;包括自动驾驶技术、计算机视觉技术、通信技术、物…

KVM虚拟机管理

1、创建、删除快照 关机 init0 列出快照 删除快照 2、虚拟机迁移 报错 解决&#xff1a;关闭防火墙&#xff0c;关闭selinux 其他解决办法&#xff1a;kvm热迁移使用nfs共享存储报错_莉法的博客-CSDN博客

神经网络基础-神经网络补充概念-14-逻辑回归中损失函数的解释

概念 逻辑回归损失函数是用来衡量逻辑回归模型预测与实际观测之间差异的函数。它的目标是找到一组模型参数&#xff0c;使得预测结果尽可能接近实际观测。 理解 在逻辑回归中&#xff0c;常用的损失函数是对数似然损失&#xff08;Log-Likelihood Loss&#xff09;&#xff…

网络安全 Day30-运维安全项目-容器架构上

容器架构上 1. 什么是容器2. 容器 vs 虚拟机(化) :star::star:3. Docker极速上手指南1&#xff09;使用rpm包安装docker2) docker下载镜像加速的配置3) 载入镜像大礼包&#xff08;老师资料包中有&#xff09; 4. Docker使用案例1&#xff09; 案例01&#xff1a;:star::star::…

Redis-分布式锁!

分布式锁&#xff0c;顾名思义&#xff0c;分布式锁就是分布式场景下的锁&#xff0c;比如多台不同机器上的进程&#xff0c;去竞争同一项资源&#xff0c;就是分布式锁。 分布式锁特性 互斥性:锁的目的是获取资源的使用权&#xff0c;所以只让一个竞争者持有锁&#xff0c;这…

三分之一的英国大学生被欺诈

根据NatWest的一项新研究&#xff0c;去年英国大学三分之一的学生在网上遭遇欺诈。 今年5月&#xff0c;这家高街银行委托咨询公司RedBrick对来自63个城镇的3000多名英国大学生进行了调查。 尽管三分之一的受访者表示他们在过去的12个月里遇到过诈骗&#xff0c;但没有统计数…

【Unity每日一记】资源加载相关你掌握多少?

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

opencv进阶08-K 均值聚类cv2.kmeans()介绍及示例

K均值聚类是一种常用的无监督学习算法&#xff0c;用于将一组数据点分成不同的簇&#xff08;clusters&#xff09;&#xff0c;以便数据点在同一簇内更相似&#xff0c;而不同簇之间差异较大。K均值聚类的目标是通过最小化数据点与所属簇中心之间的距离来形成簇。 当我们要预测…

【C++学习手札】一文带你初识C++继承

食用指南&#xff1a;本文在有C基础的情况下食用更佳 &#x1f340;本文前置知识&#xff1a; C类 ♈️今日夜电波&#xff1a;napori—Vaundy 1:21 ━━━━━━️&#x1f49f;──────── 3:23 …