【linux线程(三)】生产者消费者模型详解(多版本)

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:Linux从入门到精通⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学更多操作系统知识
  🔝🔝


在这里插入图片描述

Linux线程

  • 1. 前言
  • 2. 初识生产者消费者模型
  • 3. 阻塞队列版本的代码实现
  • 4. 初识posix信号量
  • 5. 基于信号量的环形队列模型
  • 6. 环形队列模型的代码编写
  • 7. 总结以及拓展

1. 前言

学习进程和线程也很久了,它们具体能解决
什么问题?有什么实际的运用?

本章重点:

本篇文章着重讲解基于多线程下的
生产者消费者模型的概念以及实现.
不仅如此,文章还会拓展基于使用信号量
实现的环形队列版的生产者消费者模型


2. 初识生产者消费者模型

先回答第一个问题:
什么是生产者消费者模型?

生产者消费者模型的本质就是通过一个阻塞队列来将生产者和消费者解耦合的模型.可以将这个模型比作一个超时,生产者生产了数据(商品)后,不会直接拿给消费者使用,而是将数据(商品)先加入到超市,让超市帮我们卖数据(商品).同理,对于消费者来说它并不会直接去生产者的厂家直接拿数据(商品),而是去超市中购买数据(商品).这样生产者和消费者两个对象就解耦合了

再回答第二个问题:
这个模型有什么好处?

想象一下这个场景,假如没有超市的存在,消费者去买物品的伤害要直接到生产厂家去买,并且消费者还不知道生产厂家现在是否有物品,很容易跑空.对于生产者来说,他也不知道消费者需要哪些物品,也就不好生产,容易生产出来的物品没人问津,加入超市这个角色就可以解决上面的问题.综上所述,生产者消费者模型不仅仅将二者解耦了,并且还提高了效率,不会让两方出现跑空的情况(超市没有商品了,消费者最清楚,超市有商品时,生产者最清楚)

在这里插入图片描述
不仅如此,生产者消费者模型还支持多个线程一起进入,阻塞队列属于临界资源,所以同一时刻,不管是生产者还是消费者,都只允许一个线程进入到阻塞队列进行push或pop操作.除此之外,我们还需要两个条件变量push_cond和pop_cond,当阻塞队列已满时,push_cond条件变量会让生产者线程等待消费者线程在队列中取走数据,而当阻塞队列为空时,pop_cond条件变量会让消费者线程等待生产者往队列中加入数据,一来一回,也就是push操作会唤醒pop_cond条件变量,而pop操作会唤醒push_cond条件变量


3. 阻塞队列版本的代码实现

在的代码中一定会涉及到加解锁的问题,所以可以根据RAII思想,先设计出一个专门用于加解锁的类:

lockguard.hpp文件:

#pragma once
#include<iostream>
#include<pthread.h>
using namespace std;
class Mutex //封装pthread库的锁
{
public:Mutex(pthread_mutex_t* pmtx):_pmtx(pmtx){}void lock(){pthread_mutex_lock(_pmtx);}void unlock(){pthread_mutex_unlock(_pmtx);}~Mutex(){delete _pmtx;_pmtx = nullptr;}   
private:pthread_mutex_t* _pmtx;
};
//利用RAII思想,用对象的生命周期控制资源
class LockGuard//封装了锁后,出了作用域会自动调用析构函数,解锁!
{
public:LockGuard(pthread_mutex_t* mtx):_mtx(mtx){_mtx.lock();}~LockGuard(){_mtx.unlock();}
private:Mutex _mtx;
};

下一步,编写基于阻塞队列的模型,
在实现它之前需要先注意一些点:

下面的define语句可有可无,主要是为了图方便.第二点,检测临界资源是否就绪时要使用while而不是if,因为有时可能会错误的唤醒一个进程,如果是if语句,一旦错误唤醒,就会直接进入临界区,不合理,使用while的话,就算是错误唤醒了一个线程,只要资源不就绪也不能往后走.在下面的代码中并没有使用上上面封装的锁,这是为了方便同学们即使不封装锁也能很好的查看代码,最后,代码的解释都放在注释当中了,如果你还有问题,欢迎你来私信我

block_queue.hpp文件:

#pragma once
#include<iostream>
#include<queue>
#include<pthread.h>
#include"LockGuard.cpp"
using namespace std;
#define INIT_MTX(mtx) pthread_mutex_init(&mtx,nullptr)
#define INIT_COND(cond) pthread_cond_init(&cond,nullptr)
#define DESTORY_MTX(mtx) pthread_mutex_destroy(&mtx)
#define DESTORY_COND(cond) pthread_cond_destroy(&cond)template<class T>
class BlockQueue
{
public:BlockQueue(size_t capacity = 5):_capacity(capacity){INIT_MTX(_mtx);INIT_COND(_condisempty);INIT_COND(_condisfull);}~BlockQueue(){DESTORY_MTX(_mtx);DESTORY_COND(_condisempty);DESTORY_COND(_condisfull);}void push(const T& in){//1. 加锁pthread_mutex_lock(&_mtx);//2. 检测临界资源是否满足访问条件,不满足就利用条件变量让它等待while(isfull()){cout<<"队列已满,push等待中..."<<endl;//我是抱着锁去等待的,所以即使消费者去pop了,没有锁也只能阻塞等待,就造成死循环了!//pthread_cond_wait的第二个参数是一把锁,当成功调用wait后,传入的锁会自动释放!//被唤醒时,还是在加锁和解锁之间,wait函数会自动帮线程获取锁// 1. wait函数可能会调用失败// 2. wait函数可能存在伪唤醒的情况// 所以不能使用if语句来判断,而是用while,醒来后再判断一下是不是真的唤醒pthread_cond_wait(&_condisfull,&_mtx);//若队列满了,就去isfull变量中等待cout<<"等待成功,继续执行..."<<endl;}//3. 访问临界资源_bq.push(in);//5. 生产完后唤醒消费者(满足一定条件再唤醒)if(_bq.size() >= _capacity/2)pthread_cond_signal(&_condisempty);//4. 解锁pthread_mutex_unlock(&_mtx);}void pop(T* out){//1. 加锁pthread_mutex_lock(&_mtx);//2. 检测临界资源是否满足访问条件,不满足就利用条件变量让它等待while(isempty()){cout<<"队列为空,pop等待中..."<<endl;pthread_cond_wait(&_condisempty,&_mtx);cout<<"等待成功,继续执行..."<<endl;}//3. 访问临界资源*out = _bq.front();_bq.pop();//4. 解锁pthread_mutex_unlock(&_mtx);//5. 消费完后唤醒生产者pthread_cond_signal(&_condisfull);}bool isempty(){return _bq.size() == 0;}bool isfull(){return _bq.size() ==_capacity;}
private:queue<T> _bq;size_t _capacity;  //阻塞队列容量上限pthread_mutex_t _mtx;  //通过互斥锁保证队列push/pop时,不会被pop/push(正在生产时就来消费)//push或pop时,怎么知道队列是不是满或空呢?通过条件变量来同步信息pthread_cond_t _condisempty; //用它来表示阻塞队列是否为空的条件pthread_cond_t _condisfull; //用它来表示阻塞队列是否为满的条件
};

对于这段代码的测试代码较为繁杂,我
把代码的码云链接放出来,有兴趣的同学
下来写完可以去做一些测试:

我的码云链接


4. 初识posix信号量

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。

请思考以下问题:

在基于阻塞队列的生产者消费者模型中,生产者关心的是阻塞队列是否满了,而消费者关心的是阻塞队列是否有数据,既然它们关心的资源不同,但进入到阻塞队列就会直接加锁,是不是有一点不合理?有没有一种方法可以只让生产者与生产者之间以及消费者与消费者之间存在锁竞争?而生产者和消费者之间不存在锁竞争?答案是使用信号量!

什么是信号量?
信号量的本质是一个计数器,在使用资源前需要预定资源,也就是让信号量减一.类似于去电影院看电影需要提前买票预定座位,并且在访问完资源释放信号量,也就是让信号量加一

根据以上的信息可以简化模型:

生产者关心队列是否为满,刚开始队列是空,那么生产者的信号量就是n(队列的长度),而消费者关心队列是否有资源,刚开始队列为空,那么消费者的信号量就是0.并且在生产者push一个数据到队列后,生产者的信号量需要减一,而消费者的信号量会加一,同理,消费者从队列拿走一个数据后,生产者的信号量会加一,而消费者的信号量会减一

信号量的使用方法分为以下几步:

  • 初始化信号量
    在这里插入图片描述
  • 销毁信号量

在这里插入图片描述

  • 等待信号量(信号量减一)
    在这里插入图片描述

  • 发布信号量(信号量加一)

在这里插入图片描述

等待和发布信号量的操作又被称为PV操作


5. 基于信号量的环形队列模型

可以优化最初的生产者消费者模型:

使用一个环形队列,只有生产和消费指向环形队列中的同一个位置(队列为满或空)时,才会出现生产者和消费者互斥或同步的问题,而当生产和消费指向环形队列中的不同位置时,只需控制信号量即可

在这里插入图片描述

  • 生产者关心空间资源,使用信号量spacesem,起始值为N
  • 消费者关心数据资源,使用信号量datasem,起始值为0
  • 期望:生产者不能让消费者套圈(为空时要先让生产者先运行)
  • 期望:消费者不能超过生产者(为满时要让消费者先运行)
  • 需要两把锁,一把给生产者之间当互斥锁,另外一把给消费者用

6. 环形队列模型的代码编写

还是和之前一样,可以选择将操作信号量的函数专门写为一个类,当然也可以用原生的

sem.hpp文件中

#pragma once
#include<semaphore.h>
using namespace std;
class Sem
{
public:Sem(int value){sem_init(&_sem,0,value);}~Sem(){sem_destroy(&_sem);}void p() //PV操作分别代表信号量减一和信号量加一{sem_wait(&_sem);}void v(){sem_post(&_sem);}
private:sem_t _sem;
};

在RingQueue.hpp文件中:

#pragma once
#include<iostream>
#include<pthread.h>
#include<vector>
#include<unistd.h>
#include<semaphore.h>
using namespace std;
#include"Sem.hpp"
const int g_num = 5;
template<class T>
class RingQueue
{
public:RingQueue(int num = g_num):_num(num),_rq(num),_pushindex(0),_popindex(0),_space_sem(num)//最开始空间资源是满的,_data_sem(0)//最开始数据资源是空的{pthread_mutex_init(&pushlock,nullptr);pthread_mutex_init(&poplock,nullptr);}~RingQueue(){pthread_mutex_destroy(&pushlock);pthread_mutex_destroy(&poplock);}void push(const T& in)//生产者进行,关心空间资源,生产者们的临界资源是pushindex下标{_space_sem.p();pthread_mutex_lock(&pushlock);//对生产者们进行加锁_rq[_pushindex++] = in;_pushindex %= _num;pthread_mutex_unlock(&pushlock);_data_sem.v();}void pop(T* out)//消费者进行,关心数据资源,消费者们的临界资源也是popindex下标{//一般先申请信号量,再进行加锁_data_sem.p();pthread_mutex_lock(&poplock);//对消费者们进行加锁//竞争成功的生产者线程只有一个*out = _rq[_popindex++];_popindex %= _num;pthread_mutex_unlock(&poplock);_space_sem.v();}
private:vector<T> _rq;//环形队列可用数组表示size_t _num;//总共的元素个数size_t _pushindex = 0;//push的下标size_t _popindex = 0;//pop的下标Sem _space_sem;Sem _data_sem;pthread_mutex_t pushlock;//生产者的锁pthread_mutex_t poplock;//消费者的锁
};

关于代码的解释都在注释中,若你有疑问,欢迎私信我~


7. 总结以及拓展

生产者消费者模型是我们学习线程中的一个很重要的模型,它的思想有助于帮我们解决后续的难题,并且校招时,有可能会让手撕一个生产者消费者模型,所以同学要学扎实了


🔎 下期预告:线程池详解 🔍

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

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

相关文章

【GameFramework框架内置模块】10、本地化(Localization)

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 【GameFramework框架】系列教程目录&#xff1a; https://blog.csdn.net/q7…

哔哩哔哩秋招Java二面

前言 作者&#xff1a;晓宜 个人简介&#xff1a;互联网大厂Java准入职&#xff0c;阿里云专家博主&#xff0c;csdn后端优质创作者&#xff0c;算法爱好者 一面过后面试官叫我别走&#xff0c;然后就直接二面&#xff0c;二面比较简短&#xff0c;记录一下&#xff0c;希望可以…

绝地求生:现在购买通行证还能兑换成长型武器吗?

大家好&#xff0c;我闲游盒&#xff0c;这几天收到几位盒友的私信咨询我现在购买通行证还能获得一把成长型武器吗&#xff1f;我相信还有许多盒友也有此困惑&#xff0c;那我就在这统一回复了&#xff0c;目前距通行证和商城物资箱礼包下架还有最后16天时间&#xff0c;众所周…

js实现hash路由原理

一、简单的上下布局&#xff0c;点击左侧导航&#xff0c;中间内容跟对变化&#xff0c;主要技术使用js检测路由的onhashchange事件 效果图 二、话不多说&#xff0c;直接上代码 <!DOCTYPE html> <html lang"zh"><head><meta charset"…

FPGA控制AD7606_AD7606解读

目录 一、AD7606解读二、引脚说明三、时序图 一、AD7606解读 AD7606特点&#xff1a; 8通道同步采样模拟通道数为8分辨率&#xff1a;16bit&#xff0c;即最小采样的电压为5V/(2^16) 0,00007V&#xff0c;即数字量的1就代表模拟量的0,00007V&#xff0c;2代表0,00014V有效位数…

C语言易错知识点

1、数组长度及所占字节数 char x[] {"Hello"},y[]{H,e,l,l,o}; x数组的长度为5&#xff0c;y的长度也是5 x、y数组所占字符串为6为 51(\0)6 strlen&#xff08;&#xff09;函数得到的是数组的长度 2、%%与%的优先级 #include<stdio.h> int main(){ int a…

iOS图片占内存大小与什么有关?

1. 问&#xff1a;一张图片所占内存大小跟什么有关&#xff1f; 图片所占内存大小&#xff0c;与图片的宽高有关 我们平时看到的png、jpg、webp这些图片格式&#xff0c;其实都是图片压缩格式。通过对应的算法来优化了大小以节省网络传输与本地保存所需的资源。 但是当我们加…

再谈EMC Unity存储系统内存DIMM问题

以前写过一篇关于EMC Unity 存储系统的DIMM的介绍文章&#xff0c;但是最近还是遇到很多关于内存的问题&#xff0c;还有一些退货&#xff0c;所以有必要再写一篇关于EMC Unity 内存方面的问题&#xff0c;供朋友们参考。如果还有疑问&#xff0c;可以加vx&#xff1a;StorageE…

【黑马头条】-day01环境搭建SpringBoot-Cloud-Nacos

文章目录 1 环境搭建及简介2 项目介绍2.1 应用2.2 业务说明2.3 技术栈2.4 收获2.5 大纲 3 Nacos准备3.1 安装Nacos 4 初始工程搭建4.1 环境准备4.1.1 导入项目4.1.2 设置本地仓库4.1.3 设置项目编码格式 4.2 全局异常4.2.1 自动装配 4.3 工程主体结构 5 登录功能开发5.1 需求分…

echart多折线图堆叠 y轴和实际数据不对应

当使用 ECharts 绘制堆叠折线图时&#xff0c;有时会遇到 y 轴与实际数据不对应的问题。 比如明明值是50&#xff0c;但折线点在y轴的对应点却飙升到了二百多 解决办法&#xff1a; 查看了前端代码发现在echart的图表中有一个‘stack’的属性&#xff0c;尝试把他删除之后y轴的…

算法体系-11 第十一节:二叉树基本算法(上)

一 两链表相交 1.1 题目描述 给定两个可能有环也可能无环的单链表&#xff0c;头节点head1和head2。请实现一个函数&#xff0c;如果两个链表相交&#xff0c;请返回相交的 第一个节点。如果不相交&#xff0c;返回null 【要求】 如果两个链表长度之和为N&#xff0c;时间复杂…

静电无处不在:揭秘液晶显示屏静电防护的“大师级“策略

静电&#xff0c;仿佛是电子产品制造过程中的隐形杀手&#xff0c;尤其对于液晶显示屏等精密电子元器件的影响更是不可小觑。然而&#xff0c;面对这一挑战&#xff0c;有些制造商采取了一系列超越寻常的静电防护措施。今天&#xff0c;我们将揭开他们的"大师级"策略…

利用Android studio 查看模拟器中数据文件

打开Android studio &#xff0c;然后按照下图选择 然后会在右侧打开一个这样子的管理弹窗 找到 data/data/your project file 你的缓存跟下载的文件就都在里面了

BigDecimal保留两位小数失败问题

文章目录 背景问题解决如何测试代码 背景 测试时发现在线swagger测试会自动处理BigDecimal小数点后面的数字&#xff0c;就是有零的会都给你去掉&#xff0c;比如9.000与9.500到最后都会被swagger处理成9跟9.5。使用postman测是最准的&#xff0c;测出来的就是9.000跟9.500。 …

数据库基本内容与安装MySQL数据库

目录 一.数据库基本内容 1.数据 &#xff08;1&#xff09;描述事物的符号记录 &#xff08;2&#xff09;包括数字&#xff0c;文字、图形、图像、声音、档案记录等 &#xff08;3&#xff09;以“记录”形式按统一的格式进行存储 2.表 &#xff08;1&#xff09;将不同…

【Linux】基础 IO(动静态库)-- 详解

一、前言 为什么要使用别人的代码&#xff1f; 主要是为了提高程序开发的效率和程序的健壮性。 当别人把功能都实现了&#xff0c;然后我们再基于别人的代码去做二次开发&#xff0c;那么效率当然就提高了。其次&#xff0c;这里基于的别人当然不是随便找的一个人&#xff0c;…

[Qt学习笔记]Qt鼠标事件mouseMoveEvent实时获取图像的坐标和像素值

目录 1、介绍2、效果展示3、实现过程3.1 图像的加载和显示3.2 设置鼠标跟踪事件激活3.3 实现代码 4、源码展示 1、介绍 上一篇介绍了使用OpenCV的setMouseCallback回调函数实现获取鼠标点击点的图像坐标和像素值&#xff0c;本篇使用鼠标事件mouseMoveEvent函数来实现实时获取…

OPPO 后端二面,凉凉。。。

美众议院通过 TikTok 法案 之前我们讲了 老美要求字节跳动在 165 天内剥离短视频应用 TikTok&#xff0c;当时的最新进度是 TikTok 给 1.7 亿美国用户发弹窗&#xff0c;发动用户群众给国会打电话进行抗议。 但显然这点力度的抗议并不会造成什么实质影响。 昨晚&#xff0c;美国…

精读《useRef 与 createRef 的区别》

1 引言 useRef 是常用的 API&#xff0c;但还有一个 createRef 的 API&#xff0c;你知道他们的区别吗&#xff1f;通过 React.useRef and React.createRef: The Difference 这篇文章&#xff0c;你可以了解到何时该使用它们。 2 概述 其实原文就阐述了这样一个事实&#xf…

【EDSR】《Enhanced Deep Residual Networks for Single Image Super-Resolution》

CVPR workshops-2017 首尔大学 code&#xff1a; https://github.com/limbee/NTIRE2017/tree/masterhttps://github.com/sanghyun-son/EDSR-PyTorch 文章目录 1 Background and Motivation2 Related Work3 Advantages / Contributions4 Method4.1 Residual blocks4.2 Single…