并发编程之生产者消费者模型

什么是生产者消费者模型

生产者消费者模型是多线程中一个比较典型的模型。

打个比方:你是一个客户,你去超市里买火腿肠。

这段话中的 "你"就是消费者, 那么给超市提供火腿肠的供货商就是生产者。超市呢?超市是不是被所有人所共享?大家都可以去访问超市,所以这里的超市是一份临界资源。

所以生产者消费者有三种关系,两种角色,一个交易场所。

三种关系:

1.生产者与生产者

2.消费者与消费者

3.生产者与消费者

生产者与生产者是竞争关系,因为厂商之间互相竞争。所以生产与生产者是互斥关系。

消费者与消费者其实也是竞争关系,但是因为商品够多,而消费者消费速度太慢,所以没有明显的区别。但如果世界上只剩下最后一瓶矿泉水了,那是不是大家都会去抢呢? 所以消费者与消费者其实也是互斥关系。

生产者与消费者也是竞争关系,我们生产者和消费者看成两个线程,超市看成一份临界资源。那么这两个线程是不是都要访问这个临界资源?既然都要访问这个临界资源,那么生产和消费者也是互斥关系。但不仅仅是互斥,因为生产者把超市装满了,是不是要等待用户来消费?同理如果超市空了,消费者是不是要等待生产者来供货?所以生产和消费者还有一层关系,那就是同步

两种角色

生产者与消费者

一个交易场所

一份临界资源,生产者向临界资源提供数据,消费者从临界资源中拿数据。

有没有发现生产与消费者模型很像管道?没错,管道就是典型的生产者与消费者模型。

这是一个多生产者多消费者的模型。

在这里插入图片描述

接下来我们就来实现一个基于阻塞队列的生产者消费者模型。这里的阻塞队列冲当的就是临界资源,生产者把数据放进阻塞队列,消费者把数据从阻塞队列中拿出。

锁的封装

首先我们用RAII风格的锁。

MyLock类

#include<pthread.h> 
class MyLock
    {
    public:
        MyLock(pthread_mutex_t* pmtx): _pmtx(pmtx){}
        void Lock(){ pthread_mutex_lock(_pmtx);}
        void Unlock() { pthread_mutex_unlock(_pmtx);}
    private:
        pthread_mutex_t* _pmtx;
    };

LockGuard类

#include<pthread.h>
class LockGuard
    {
    public:
        LockGuard(pthread_mutex_t* pmtx):_mtx(pmtx){
            _mtx.Lock();
        }
        ~LockGuard()
        {
            _mtx.Unlock();
        }

    private:
        MyLock _mtx;
    };

这个类的构造函数是加锁,析构函数是解锁。所以我们只需要创建一个这个类的对象的代码和临界资源的代码放在一起,就可以实现加锁和解锁了。这种方式可以避免有时候解锁忘记写了导致死锁的问题。

阻塞队列的实现

block_queue类的声明

#include<queue>
#include<pthread.h>
#include<iostream>
#include "Task.hpp"
#include "LockGuard.hpp" 
#define DEFAULT_NUM 5
template<class T> //因为不确定阻塞队列放的数据类型, 所以用模板参数
    class block_queue
    {
        private:
        size_t _num; //阻塞队列的容量
        std::queue<T> _blockqueue;  //阻塞队列
        pthread_mutex_t _mtx;  //锁
        pthread_cond_t _full;  //条件变量,让生产者在阻塞队列为满时进行等待
        pthread_cond_t _empty;  //条件变量,让消费者在阻塞队列为空时进行等待
        public: 
        block_queue(size_t num = DEFAULT_NUM); //构造函数

        ~block_queue(); // 析构
        //生产者生产
        void Push(const T& task);
        // 消费者消费
        void Pop(T* out);

        private:
        //让当前线程在指定的条件变量下等待
        void Wait(pthread_cond_t* cond) {pthread_cond_wait(cond,&_mtx);}
        //唤醒指定条件变量下等待的线程
        void Wakeup(pthread_cond_t* cond) {pthread_cond_signal(cond);}
        //判断阻塞队列是否满了
        bool isfull() { return _blockqueue.size() == _num;}
        //判断阻塞队列是否为空
        bool isempty() { return _blockqueue.size() == 0;}

    };

我们的阻塞队列实际上只提供2个操作,一个是push(生产者放数据),一个是pop(消费者拿数据)。

block_queue类的实现


#define DEFAULT_NUM 5
template<class T>
    class block_queue
    {
        private:
        size_t _num;
        std::queue<T> _blockqueue; 
        pthread_mutex_t _mtx; 
        pthread_cond_t _full; 
        pthread_cond_t _empty; 

        public: 
        block_queue(size_t num = DEFAULT_NUM) : _num(num){
            pthread_mutex_init(&_mtx,nullptr);
            pthread_cond_init(&_full,nullptr);
            pthread_cond_init(&_empty,nullptr);
        }
        ~block_queue()
        {
            pthread_mutex_destroy(&_mtx);
            pthread_cond_destroy(&_full);
            pthread_cond_destroy(&_empty);
        }

        //生产者生产
        void Push(const T& task)
        {
            LockGuard lockguard(&_mtx); //加锁,出了作用域自动解锁
            while(isfull()) Wait(&_full); //生产队列已满,生产者在full条件变量下等待
            //被唤醒后添加任务到生产队列
            _blockqueue.push(task);
            printf("%p 生产了一个任务 : %d %c %d\n",pthread_self(),task._x,task._op,task._y); //这是对任务的打印....暂且无视,等Task类实现完后看结果的
            Wakeup(&_empty); //唤醒消费者
        }

        // 消费者消费
        void Pop(T* out)
        {
            LockGuard lockguard(&_mtx) ;//加锁,出了作用域自动解锁
            while(isempty()) Wait(&_empty); //生产队列已空,消费者进入等待 
            //被唤醒后添加任务到生产队列
            *out = _blockqueue.front(); //提取任务
            _blockqueue.pop(); //队列pop
            Wakeup(&_full);
        }
        private:
        void Wait(pthread_cond_t* cond) {pthread_cond_wait(cond,&_mtx);}
        void Wakeup(pthread_cond_t* cond) {pthread_cond_signal(cond);}
        bool isfull() { return _blockqueue.size() == _num;}
        bool isempty() { return _blockqueue.size() == 0;}
    };

Task类实现

我们可以往阻塞队列里面放数据,当然也可以往里面放一个任务。这里我们就创建一个加减乘除取模运算的任务类。

#include <iostream>

class Task{
    public:
        Task(){}
        Task(int x, char op,int y):_x(x),_op(op),_y(y),_iserror(false){}
    
        void Runing()
        {
            int ret = 0;
            switch(_op)
            {
                case '+' : ret = _x + _y; break; 
                case '-' : ret = _x - _y; break;
                case '*' : ret = _x * _y; break;
                case '/' :
                { 
                    if(_y) ret = _x / _y;
                    else _iserror = true;
                    break;
                }
                case '%' :
                { 
                    if(_y) ret = _x % _y;
                    else _iserror = true;
                    break;
                }
                default: _iserror = true; 
            }
            if(_iserror) std::cout << "result error" << std::endl;  //如果结果错误打印错误
            else std::cout << _x << _op << _y << "=" << ret << std::endl; //如果结果正确打印完整式子
        }
    public:
        int _x; //第一个操作数
        char _op; //操作符
        int _y; //第二个操作数
        bool _iserror; //结果是否错误
    };

Main

`

#include "BlockQueue.hpp"
#include <time.h>
#include<unistd.h>
#include<string>

#define CONNUM 5 
#define PRODNUM 2

//生产者放任务
void* ProcuderRuning(void* args)
{
    wyl::block_queue<wyl::Task>* bq = (wyl::block_queue<wyl::Task>*)args;
    while(1)
    {
        int x = rand() % 10 + 1;
        int y =  rand()%20;
        char op = "+-*/%"[rand() % 5];
        bq->Push(wyl::Task(x,op,y)); //往阻塞队列中放任务
    }
}

//消费不断拿任务
void* ConsumerRuning(void* args)
{
    wyl::block_queue<wyl::Task>* bq = (wyl::block_queue<wyl::Task>*)args;
    while(1)
    {
        wyl::Task t; 
        bq->Pop(&t); //从阻塞队列中拿任务
        printf("%p 消费了一个任务",pthread_self());
        t.Runing(); //处理任务
        sleep(1); //让消费者不要频繁消费太快,这样阻塞队列满了会等待消费者
    }
}

int main()
{
 	pthread_t con[CONNUM]; 
    pthread_t prod[PRODNUM]; 
    srand((unsigned int)0); //随机数种子
    //创造等待队列
    wyl::block_queue<wyl::Task>* bq = new wyl::block_queue<wyl::Task>(5);

    //创建生产者线程
    for(int i = 0 ; i < PRODNUM ; i++)
    {
        std::string name = "prodcuer ";
        name += std::to_string(i+1); 
        pthread_create(prod + i,nullptr,ProcuderRuning,(void*)bq);
    }
    
    //创建消费者线程
    for(int i = 0 ; i < CONNUM ; i++)
    {
        std::string name = "consumer ";
        name += std::to_string(i+1); 
        pthread_create(con + i,nullptr,ConsumerRuning,(void*)bq);
    }
    
    //等待线程
    for(int i = 0 ; i < PRODNUM ; i++)
    {
        pthread_join(prod[i],nullptr);
    }

    for(int i = 0 ; i < CONNUM ; i++)
    {
        pthread_join(con[i],nullptr);
    }

    return 0;
}

`

消费者慢消费,生产者快生产的执行结果:

在这里插入图片描述

生产者慢生产,消费者快消费的运行结果:

在这里插入图片描述

我们会发现,任务井然有序的执行。生产者放了数据后通知消费拿,消费者把数据拿完又会通知生产者放。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

php连接sqlserver 安装sqlserver 驱动windows系统

第一步下载Windows 上的 Microsoft ODBC Driver for SQL Server ODBC 驱动程序 Microsoft ODBC Driver for SQL Server 直接下载安装即可&#xff0c;安装后可查看安装版本 第二步&#xff1a;下载php_sqlsrv 驱动 安装解压后&#xff0c;会有对应php版本的驱动文件&#xf…

草图一键生成静态网页,看看这个开源项目

借助GPT-4V视觉模型&#xff0c;可以轻松的将一张草图生成一个静态页面。现在这已经不是什么稀奇事了。主要是分享一下它的Prompt&#xff0c;很简单&#xff0c;用户画好草图后&#xff0c;将草图保存成png图片&#xff0c;传给GPT-4V&#xff0c;然后GPT返回一个标准的HTML&a…

Navicat for mysql 无法连接到虚拟机的linux系统下的mysql

原创/朱季谦 最近在linux Centos7版本的虚拟机上安装了一个MySql数据库&#xff0c;发现本地可以正常ping通虚拟机&#xff0c;但Navicat则无法正常连接到虚拟机里的MySql数据库&#xff0c;经过一番琢磨&#xff0c;发现解决这个问题的方式&#xff0c;很简单&#xff0c;总共…

9.MyBatis-Plus

1、前期准备 a. 创建数据库 CREATE TABLE USER (id BIGINT(20)NOT NULL COMMENT 主键ID,NAME VARCHAR(30)NULL DEFAULT NULL COMMENT 姓名,age INT(11)NULL DEFAULT NULL COMMENT 年龄,email VARCHAR(50)NULL DEFAULT NULL COMMENT 邮箱,PRIMARY KEY (id) );INSERT INTO user…

亚马逊云科技云存储服务指南

文章作者&#xff1a;Libai 高效的云存储服务对于现代软件开发中的数据管理至关重要。亚马逊云科技云存储服务提供了强大的工具&#xff0c;可以简化工作流程并增强数据管理能力。 亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、开发案例、技术专栏…

常见的业务分析方法

树状结构分析 由于数据维度的丰富性&#xff0c;不知从哪个维度开始分析&#xff0c;如果每个维度都尝试下探非常耗时。这时可以考虑从总体指标入手&#xff0c;逐层分解总体指标&#xff0c;形成下钻式结构。 分析思路如下&#xff1a; 梳理行业内常见指标&#xff1b;将指…

【Proteus仿真】【51单片机】锂电池管理系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使用LCD1602显示模块、DS18B20温度传感器、PCF8691 ADC模块、按键、LED蜂鸣器模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示温度…

如何做到百万数据半小时跑批结束

什么是跑批 跑批就是应用程序定时对数据的批量处理。 跑批有以下特性&#xff1a; 大数据量&#xff1a;批量任务一般伴随着大量的数据处理 自动化&#xff1a;要求制定时间或频率自动运行 性能&#xff1a;要求在指定时间内完成批处理任务 健壮性&#xff1a;针对于异常数…

BI 数据可视化平台建设(2)—筛选器组件升级实践

作者&#xff1a;vivo 互联网大数据团队-Wang Lei 本文是vivo互联网大数据团队《BI数据可视化平台建设》系列文章第2篇 -筛选器组件。 本文主要介绍了BI数据可视化平台建设中比较核心的筛选器组件&#xff0c; 涉及组件分类、组件库开发等升级实践经验&#xff0c;通过分享一些…

anaconda中安装pytorch和TensorFlow环境并在不同环境中安装kernel

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

Redis最新2023年面试题高级面试题及附答案解析(2)【Redis最新2023年面试题高级面试题及附答案解析-第三十九刊】

文章目录 Redis最新2023年面试题高级面试题及附答案解析(2)01、Redis 集群方案应该怎么做&#xff1f;都有哪些方案&#xff1f;02、Redis 的内存用完了会发生什么&#xff1f;03、怎么测试 Redis 的连通性&#xff1f;04、Redis 集群会有写操作丢失吗&#xff1f;为什么&#…

【java学习—十五】Thread类的有关方法(3)

文章目录 1. 基本方法2. 线程的优先级3. 进阶方法3.1. 举例3.1.1. 线程让步3.1.2. join() 方法3.1.3. sleep()方法3.1.4. stop() 方法3.1.4. isAlive() 方法 1. 基本方法 方法名作用void start()启动线程&#xff0c;并执行对象的 run() 方法run()线程在被调度时执行的操作Str…

T13级专家被毕业?!研发大牛被裁带来的警示丨IDCF

2005年加入腾讯&#xff0c;腾讯第一位Web前端专家&#xff0c;T13职级&#xff0c;今年1月仍是腾讯前端最高专家。 在47岁的时候&#xff0c;拥有这样简历的前端大牛黄希彤被腾讯裁员。 黄希彤夫人在小红书上透露&#xff1a;&#xff08;黄希彤&#xff09;在鹅厂工作了15年…

大语言模型量化方法对比:GPTQ、GGUF、AWQ

在过去的一年里&#xff0c;大型语言模型(llm)有了飞速的发展&#xff0c;在本文中&#xff0c;我们将探讨几种(量化)的方式&#xff0c;除此以外&#xff0c;还会介绍分片及不同的保存和压缩策略。 说明&#xff1a;每次加载LLM示例后&#xff0c;建议清除缓存&#xff0c;以…

ROS 学习应用篇(六)参数的使用与编程

node可能不在一个电脑里但是这些服务的参数信息是共享的&#xff0c;因为话题Topic是异步的所以只有服务Service有实时参数信息可以调用。 接下来将演示服务参数信息的调用与修改。 创建功能包(工作空间src文件夹下) catkin_create_pkg learning_parameter roscpp rospy std…

MySQL中全文索引和普通索引的区别

MySQL中的全文索引&#xff08;Full-Text Index&#xff09;和普通索引&#xff08;比如B-Tree索引&#xff09;是为了提高查询效率而设计的&#xff0c;但它们适用于不同的场景和查询类型。 普通索引&#xff08;如B-Tree索引&#xff09; 适用场景&#xff1a;普通索引适用于…

jsp中使用PDF.js实现pdf文件的预览

本文介绍的是在使用jsp作为模板引擎的spring-mvc项目中&#xff0c;如何利用 PDF.js实现pdf文件的预览。 1、下载 PDF.js Getting Started (mozilla.github.io) 下载解压后其中有两个目录&#xff0c;直接将这两个文件夹放到项目的web资源目录中。此时相当于把PDF.js这个项目也…

3ds max 2024 V-Ray 6 ACES workflow 工作流设置

ACES的流程包括2个设置&#xff1a; 1、环境设置&#xff1b;2、贴图设置&#xff1a; 一、环境设置&#xff1a;3ds max 2024已经内置了OCIO文件&#xff1b;设置一下即可&#xff1b; 二、贴图设置&#xff1a; 所有类型贴图加载有默认和加后缀2种方法&#xff1a; 第一…

使用VC++设计程序使用邻域平均平滑算法、中值滤波算法、K近邻均值滤波器(KNNF)进行滤波

VC实现若干种图像滤波技术 文章目录 VC实现若干种图像滤波技术实验内容邻域平均平滑算法1. 原理2. 实验代码3. 实验现象 中值滤波算法1. 原理2. 实验代码3.实验现象 K近邻均值滤波算法&#xff08;KNNF&#xff09;1. 原理2. 实验代码实验现象 实验内容 实验要求&#xff1a; …

【机器学习】 特征工程:特征预处理,归一化、标准化、处理缺失值

特征预处理采用的是特定的统计方法&#xff08;数学方法&#xff09;将数据转化为算法要求的数字 1. 数值型数据 归一化&#xff0c;将原始数据变换到[0,1]之间 标准化&#xff0c;数据转化到均值为0&#xff0c;方差为1的范围内 缺失值&#xff0c;缺失值处理成均值、中…
最新文章