当前位置: 首页 > news >正文

【Linux】生产者消费者模型 - 详解

目录

一.生产者消费者模型概念

1.为何要使用生产者消费者模型

2.生产者消费者之间的关系

3.生产者消费者模型的优点

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

1.在阻塞队列中的三种关系

2.BlockingQueue.hpp - 阻塞队列类

3.LockGurad.hpp - RAII互斥锁类

4.Task.hpp - 在阻塞队列中生产/消费的任务类

5.CPTest.cc - 测试

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

1.在环形队列中的三种关系

2.环形队列vs阻塞队列

3.CircularQueue.hpp - 环形队列类

4.LockGuard.hpp - RAII互斥锁类

5.Task.cpp - 任务类

6.CPTest.cc - 测试

四. 写在最后

1.关于条件变量的伪唤醒问题

2.关于信号量和锁的先后顺序问题 


一.生产者消费者模型概念

1.为何要使用生产者消费者模型

生产者消费者模式就是通过一个"容器"来解决生产者与消费者之间的强耦合问题, 生产者和消费者彼此之间不直接通讯, 而是通过特定"容器"来进行通讯, 所以生产者生产完数据之后不需要等待消费者处理, 直接扔给"容器", 消费者不直接找生产者索要数据, 而是从"容器"中拿数据, "容器"其实就是由特定的数据结构编写的缓冲区, 平衡了生产者和消费者的处理能力, 通过生产消费之间的"容器"达到解耦的目的

2.生产者消费者之间的关系

一个场所, 也就是上述"容器"

二个角色, 生产者与消费者

三种关系

        生产者 - 生产者: 互斥关系

        消费者 - 消费者: 互斥关系

        生产者 - 消费者: 互斥关系, 同步关系

生产者消费者模型分为两种

1.单生产单消费

2.多生产多消费

3.生产者消费者模型的优点

1.将生产者消费者的强耦合关系解耦

2.支持并发

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

1.在阻塞队列中的三种关系

生产者与生产者之间互斥

消费者与消费者之间互斥

生产者与消费者之间既互斥又同步

同步关系使用条件变量

2.BlockingQueue.hpp - 阻塞队列类

#pragma once#include <iostream>
#include <queue>
#include "LockGurad.hpp"template <class T>
class BlockingQueue
{
public:static BlockingQueue<T> *GetInst(){return &_sInst;}public:void push(const T& in){LockGurad lock(&_mutex); // RAII锁// 如果阻塞队列满了就阻塞while(isFull()){pthread_cond_wait(&_isFullCond, &_mutex); // 已满, 阻塞}_bq.push(in);pthread_cond_signal(&_isEmptyCond); // 已经添加了一个, 唤醒已空条件阻塞}void pop(T* out){LockGurad lock(&_mutex); // RAII锁// 如果阻塞队列空了就阻塞while(isEmpty()){pthread_cond_wait(&_isEmptyCond, &_mutex); // 已空, 阻塞}*out = _bq.front();_bq.pop();pthread_cond_signal(&_isFullCond); // 已经取走了一个, 唤醒已满条件阻塞}~BlockingQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_isFullCond);pthread_cond_destroy(&_isEmptyCond);}private:bool isFull() // 判满{return _bq.size() == _capacity;}bool isEmpty() // 判空{return _bq.size() == 0;}private:std::queue<T> _bq;          // 阻塞队列size_t _capacity;           // 阻塞队列最大容量pthread_mutex_t _mutex;      // 保护阻塞队列的锁pthread_cond_t _isFullCond;  // 判断是否为满pthread_cond_t _isEmptyCond; // 判断是否为空private:// 单例模式BlockingQueue(size_t capacity = 5) : _capacity(capacity){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_isFullCond, nullptr);pthread_cond_init(&_isEmptyCond, nullptr);}BlockingQueue(const BlockingQueue<T> &copy) = delete;BlockingQueue<T> &operator=(const BlockingQueue<T> &copy) = delete;static BlockingQueue<T> _sInst;
};template <class T>
BlockingQueue<T> BlockingQueue<T>::_sInst;

3.LockGurad.hpp - RAII互斥锁类

#pragma once#include <pthread.h>class LockGurad
{
public:LockGurad(pthread_mutex_t* mutex):_mutex(mutex){pthread_mutex_lock(_mutex);}~LockGurad(){pthread_mutex_unlock(_mutex);}private:pthread_mutex_t* _mutex;
};

4.Task.hpp - 在阻塞队列中生产/消费的任务类

#pragma oncetypedef int(* func_t)(int, int);struct Task
{Task(){};Task(int x, int y, func_t func):_x(x),_y(y),_func(func){}int operator()(){return _func(_x, _y);}int _x;int _y;func_t _func;
};

5.CPTest.cc - 测试

#include "BlockingQueue.hpp"
#include "Task.hpp"
#include "time.h"
#include "stdlib.h"
#include "sys/types.h"
#include "unistd.h"int myAdd(int x, int y)
{return x + y;
}void *Consumer(void *args)
{BlockingQueue<Task> *bqsInst = (BlockingQueue<Task> *)args;while (1){// 消费任务Task t;bqsInst->pop(&t);std::cout << pthread_self() << " consumer: " << t._x << "+" << t._y << "=" << t() << std::endl;sleep(1);}return nullptr;
}void *Provider(void *args)
{BlockingQueue<Task> *bqsInst = (BlockingQueue<Task> *)args;while (1){// 制作任务int x = rand() % 100 + 1;int y = rand() % 100 + 1;Task t(x, y, myAdd);// 添加任务bqsInst->push(t);std::cout << pthread_self() << " productor: " << t._x << "+" << t._y << "=?" << std::endl;sleep(1);}return nullptr;
}int main()
{srand((unsigned int)time(nullptr) ^ getpid() ^ 0x123);// 获取单例指针BlockingQueue<Task> *bqsInst = BlockingQueue<Task>::GetInst();// 单生产单消费//pthread_t c, p;//pthread_create(&c, nullptr, Consumer, bqsInst);//pthread_create(&p, nullptr, Provider, bqsInst);// 多生产多消费pthread_t c[2], p[3];pthread_create(c, nullptr, Consumer, bqsInst);pthread_create(c + 1, nullptr, Consumer, bqsInst); pthread_create(p, nullptr, Provider, bqsInst);pthread_create(p + 1, nullptr, Provider, bqsInst);pthread_create(p + 2, nullptr, Provider, bqsInst);pthread_join(c[0], nullptr);pthread_join(c[1], nullptr);pthread_join(p[0], nullptr);pthread_join(p[1], nullptr);pthread_join(p[2], nullptr);return 0;
}

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

数据结构: 环形队列, 传送入口: http://t.csdn.cn/p1OXv

1.在环形队列中的三种关系

生产者与生产者之间互斥

消费者与消费者之间互斥

生产者与消费者之间同步

同步关系使用信号量

生产消费不需要互斥, 即分别用两把锁

2.环形队列vs阻塞队列

环形队列对比阻塞队列的优势: 生产与消费可以同时进行, 这是因为环形数据结构的特性, 以及信号量sem共同支持的

为什么?

对于阻塞队列而言

当阻塞队列中还存在有数据时, 生产者与消费者必然不会访问到同一个数据, 但是当队列为空, 如果不将生产与消费互斥的话, 就很可能在生产的同时又在消费, 消费的可能是正在生产中的数据, 就可能发生问题

对于环形队列而言

生产者与消费者的下标, 只有在环形队列满或空时指向同一位置, 通过信号量进行同步之后, 不可能会发生同时在同一位置生产消费的情况

生产消费指向同一位置, 只有两种情况

1.环形队列为满, 此时该位置是还存有未消费的数据, 没有多余的生产信号量, 该位置只可能存在消费行为

2.环形队列为空, 此时该位置不存有任何数据, 没有多余的消费信号量, 该位置只可能存在生产行为

3.CircularQueue.hpp - 环形队列类

#include <vector>
#include <semaphore.h>
#include "LockGurad.hpp"template <class T>
class CircularQueue
{
public:CircularQueue(size_t capacity) : _capacity(capacity), _cStep(0), _pStep(0){_v.resize(capacity);sem_init(&_spaceSem, 0, capacity);sem_init(&_dataSem, 0, 0);pthread_mutex_init(&_cMtx, nullptr);pthread_mutex_init(&_pMtx, nullptr);}void push(const T &in){// 这里把信号量加在锁前面, 进一步提高效率, 原理类似于买票看电影, 提前买票在排队检票效率更高sem_wait(&_spaceSem);{LockGurad lock(&_pMtx); // RAII加锁风格_v[_pStep++] = in;_pStep %= _capacity;}sem_post(&_dataSem);}void pop(T *out){sem_wait(&_dataSem);{LockGurad lock(&_cMtx);*out = _v[_cStep++];_cStep %= _capacity;}sem_post(&_spaceSem);}~CircularQueue(){sem_destroy(&_spaceSem);sem_destroy(&_dataSem);pthread_mutex_destroy(&_cMtx);pthread_mutex_destroy(&_pMtx);}private:std::vector<T> _v;     // 环形队列size_t _capacity; // 最大容量int _cStep; // 消费下标int _pStep; // 生产下标sem_t _spaceSem; // 空间资源信号量sem_t _dataSem;  // 数据资源信号量pthread_mutex_t _cMtx; // 生产锁pthread_mutex_t _pMtx; // 消费锁
};

4.LockGuard.hpp - RAII互斥锁类

#pragma once#include <pthread.h>class LockGurad
{
public:LockGurad(pthread_mutex_t* mutex):_mutex(mutex){pthread_mutex_lock(_mutex);}~LockGurad(){pthread_mutex_unlock(_mutex);}private:pthread_mutex_t* _mutex;
};

5.Task.cpp - 任务类

#pragma oncetypedef int(* func_t)(int, int);struct Task
{Task(){};Task(int x, int y, func_t func):_x(x),_y(y),_func(func){}int operator()(){return _func(_x, _y);}int _x;int _y;func_t _func;
};

6.CPTest.cc - 测试

#include <iostream>
#include "CircularQueue.hpp"
#include "Task.hpp"
#include <time.h>
#include <unistd.h>
#include <stdlib.h>int myAdd(int x, int y)
{return x + y;
}void* Consumer(void* args)
{CircularQueue<Task>* cq = (CircularQueue<Task>*)args;while(1){// 消费任务Task t;cq->pop(&t);std::cout << "消费: " << t._x << "+" << t._y << "=" << t() << std::endl;}
}void* Provider(void* args)
{CircularQueue<Task>* cq = (CircularQueue<Task>*)args;while(1){// 制作任务int x = rand() % 50 + 1;int y = rand() % 50 + 1;Task t(x, y, myAdd);// 添加任务cq->push(t);std::cout << "生产: " << t._x << "+" << t._y << "=?" << std::endl;sleep(1);}
}int main()
{srand((unsigned int)time(nullptr) ^ 0x123);CircularQueue<Task>* cq = new CircularQueue<Task>(5);pthread_t c[2], p[3];pthread_create(c, nullptr, Consumer, cq);pthread_create(c+1, nullptr, Consumer, cq);pthread_create(p, nullptr, Provider, cq);pthread_create(p+1, nullptr, Provider, cq);pthread_create(p+2, nullptr, Provider, cq);pthread_join(c[0], nullptr);pthread_join(c[1], nullptr);pthread_join(p[0], nullptr);pthread_join(p[1], nullptr);pthread_join(p[2], nullptr);return 0;
}

四. 写在最后

1.关于条件变量的伪唤醒问题

为什么不用if, 而使用while, 原因就是如果此时只有1个资源, 而刚刚有很多个线程在挂起等待, 为了这1个资源全部被唤醒了, 那么就一定只有一个线程真正的拿到资源, 其他线程都是伪唤醒, 避免这种问题的方法就是用while判断, 当线程被唤醒时再循环判断一次, 如果为假就会跳出循环, 如果为真说明是伪唤醒, 则继续挂起等待

2.关于信号量和锁的先后顺序问题 

值得一提的是, 环形队列中生产者消费者的同步关系由信号量来实现, 并且生产者与消费者之间没有互斥关系, 那么生产者与消费者分别使用两把锁维护其各自的互斥关系, 那么有一个值得思考的问题就是信号量和锁谁在前谁在后问题

 答案是: 信号量先申请比较好. 信号量确实访问了临界资源, 但其内部实现也是原子性的, 而且信号量类似于预定机制, 只是提前把资源预定下来, 想要访问资源, 只需要等待取走锁资源即可

相关文章:

【Linux】生产者消费者模型 - 详解

目录 一.生产者消费者模型概念 1.为何要使用生产者消费者模型 2.生产者消费者之间的关系 3.生产者消费者模型的优点 二.基于阻塞队列的生产消费模型 1.在阻塞队列中的三种关系 2.BlockingQueue.hpp - 阻塞队列类 3.LockGurad.hpp - RAII互斥锁类 4.Task.hpp - 在阻塞队…...

源码深度解析Spring Bean的加载

在应用spring 的过程中&#xff0c;就会涉及到bean的加载&#xff0c;bean的加载经历一个相当复杂的过程&#xff0c;bean的加载入口如下&#xff1a; 使用getBean&#xff08;&#xff09;方法进行加载Bean&#xff0c;最终调用的是AbstractBeanFactory.doGetBean() 进行Bean的…...

STL——priority_queue

一、priority_queue介绍及使用 1.priority_queue文档介绍 &#xff08;1&#xff09;优先队列是一种容器适配器&#xff0c;根据严格的弱排序标准&#xff0c;它的第一个元素总是它所包含的元素中最大的。 &#xff08;2&#xff09;此上下文类似与堆&#xff0c;在堆中可以…...

Springboot集成工作流Activity

介绍 官网&#xff1a;https://www.activiti.org/ 一 、工作流介绍 1.工作流&#xff08;workflow&#xff09; 就是通过计算机对业务流程自动化执行管理&#xff0c;它主要解决的是“使在多个参与这之间按照某种预定义规则自动化进行传递文档、信息或任务的过程&#xff0c…...

2023软件测试工程师涨薪攻略,3年如何达到月薪30K?

1.软件测试如何实现涨薪 首先涨薪并不是从8000涨到9000这种涨薪&#xff0c;而是从8000涨到15K加到25K的涨薪。基本上三年之内就可以实现。 如果我们只是普通的有应届毕业生或者是普通本科那我们就只能从小公司开始慢慢往上走。 有些同学想去做测试&#xff0c;是希望能够日…...

Java面试——Spring Bean相关知识

目录 1.Bean的定义 2.Bean的生命周期 3.BeanFactory及Factory Bean 4.Bean的作用域 5.Bean的线程安全问题 1.Bean的定义 JavaBean是描述Java的软件组件模型。在Java模型中&#xff0c;通过JavaBean可以无限扩充Java程序的功能&#xff0c;通过JavaBean的组合可以快速的生…...

上班在群里摸鱼,逮到一个字节8年测试开发,聊过之后羞愧难当...

老话说的好&#xff0c;这人呐&#xff0c;一旦在某个领域鲜有敌手了&#xff0c;就会闲得某疼。前几天我在上班摸鱼刷群的时候认识了一位字节测试开发大佬&#xff0c;在字节工作了8年&#xff0c;因为本人天赋比较高&#xff0c;平时工作也兢兢业业&#xff0c;现在企业内有一…...

HTTP、WebSocket和Socket.IO

一、HTTP协议 HTTP协议是Hyper Text Transfer Protocol&#xff08;超文本传输协议&#xff09;。HTTP 协议和 TCP/IP 协议族内的其他众多的协议相同&#xff0c; 用于客户端和服务器之间的通信。请求访问文本或图像等资源的一端称为客户端&#xff0c; 而提供资源响应的一端称…...

Fluent Python 笔记 第 11 章 接口:从协议到抽象基类

本章讨论的话题是接口:从鸭子类型的代表特征动态协议&#xff0c;到使接口更明确、能验证实现是否符合规定的抽象基类(Abstract Base Class&#xff0c;ABC)。 11.1 Python 文化中的接口和协议 对 Python 程序员来说&#xff0c;“X 类对象”“X 协 议”和“X 接口”都是一个…...

【Spark分布式内存计算框架——Spark Core】11. Spark 内核调度(下)

8.5 Spark 基本概念 Spark Application运行时&#xff0c;涵盖很多概念&#xff0c;主要如下表格&#xff1a; 官方文档&#xff1a;http://spark.apache.org/docs/2.4.5/cluster-overview.html#glossary Application&#xff1a;指的是用户编写的Spark应用程序/代码&#x…...

Java中的函数

1.String.trim() : 主要有2个用法&#xff1a; 1、就是去掉字符串中前后的空白&#xff1b;这个方法的主要可以使用在判断用户输入的密码之类的。 2、它不仅可以去除空白&#xff0c;还可以去除字符串中的制表符&#xff0c;如 ‘\t’,\n等。 2.Integer.parseInt() : 字符串…...

实验6-霍纳法则及变治技术

目录 1.霍纳法则(Horners rule) 2.堆排序 3.求a的n次幂 1.霍纳法则(Horners rule) 【问题描述】用霍纳法则求一个多项式在一个给定点的值 【输入形式】输入三行,第一行是一个整数n,表示的是多项式的最高次数;第二行多项式的系数组P[0...n](从低到高存储);第三行是…...

IP地址:揭晓安欣警官自证清白的黑科技

《狂飙》这部电视剧&#xff0c;此从播出以来可谓是火爆了&#xff0c;想必大家都是看过的。剧中&#xff0c;主人公“安欣”是一名警察。一直在与犯罪分子做斗争。 莽村的李顺案中&#xff0c;有匿名者这个案件在网上发帖恶意造谣&#xff0c;说安欣是黑恶势力的保护伞&#…...

考研复试机试 | C++

目录1.盛水最多的容器<11>题目代码&#xff1a;2.整数转罗马数字题目&#xff1a;代码&#xff1a;3. 清华大学机试题 abc题目题解4.清华大学机试题 反序数题目描述代码对称平方数题目代码&#xff1a;5. 杭电上机题 叠筐题目&#xff1a;代码pass&#xff1a;关于清华大…...

第四章.误差反向传播法—误差反向传播法实现手写数字识别神经网络

第四章.误差反向传播法 4.3 误差反向传播法实现手写数字识别神经网络 通过像组装乐高积木一样组装第四章中实现的层&#xff0c;来构建神经网络。 1.神经网络学习全貌图 1).前提&#xff1a; 神经网络存在合适的权重和偏置&#xff0c;调整权重和偏置以便拟合训练数据的过程称…...

IB学习者的培养目标有哪些?

IB课程强调要培养年轻人的探究精神&#xff0c;在富有渊博知识的同时&#xff0c;更要勤于思考&#xff0c;敢于思考&#xff0c;尊重和理解跨文化的差异&#xff0c;坚持原则维护公平&#xff0c;让这个世界充满爱与和平&#xff0c;让这个世界变得更加美好。上一次我们为大家…...

C++类基础(十三)

类的继承 ● 通过类的继承&#xff08;派生&#xff09;来引入“是一个”的关系&#xff08; 17.2 — Basic inheritance in C&#xff09; – 通常采用 public 继承&#xff08; struct V.S. class &#xff09; – 注意&#xff1a;继承部分不是类的声明 – 使用基类的指针…...

03 OpenCV图像运算

文章目录1 普通加法1 加号相加2 add函数2 加权相加3 按位运算1 按位与运算2 按位或运算、非运算4 掩膜1 普通加法 1 加号相加 在 OpenCV 中&#xff0c;图像加法可以使用加号运算符&#xff08;&#xff09;来实现。例如&#xff0c;如果要将两幅图像相加&#xff0c;可以使用…...

【C语言学习笔记】:动态库

一、动态库 通过之前静态库那篇文章的介绍。发现静态库更容易使用和理解&#xff0c;也达到了代码复用的目的&#xff0c;那为什么还需要动态库呢&#xff1f; 1、为什么还需要动态库&#xff1f; 为什么需要动态库&#xff0c;其实也是静态库的特点导致。 ▶ 空间浪费是静…...

Zookeeper

zookeeper是一个分布式协调服务。所谓分布式协调主要是来解决分布式系统中多个进程之间的同步限制&#xff0c;防止出现脏读&#xff0c;例如我们常说的分布式锁。 zookeeper中的数据是存储在内存当中的&#xff0c;因此它的效率十分高效。它内部的存储方式十分类似于文件存储…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

听写流程自动化实践,轻量级教育辅助

随着智能教育工具的发展&#xff0c;越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式&#xff0c;也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建&#xff0c;…...

C++:多态机制详解

目录 一. 多态的概念 1.静态多态&#xff08;编译时多态&#xff09; 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1&#xff09;.协变 2&#xff09;.析构函数的重写 5.override 和 final关键字 1&#…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

【java】【服务器】线程上下文丢失 是指什么

目录 ■前言 ■正文开始 线程上下文的核心组成部分 为什么会出现上下文丢失&#xff1f; 直观示例说明 为什么上下文如此重要&#xff1f; 解决上下文丢失的关键 总结 ■如果我想在servlet中使用线程&#xff0c;代码应该如何实现 推荐方案&#xff1a;使用 ManagedE…...