【Linux】生产者和消费者模型

news2024/11/22 13:15:07

      • 生产者和消费者概念
      • 基于BlockingQueue的生产者消费者模型
      • 全部代码

生产者和消费者概念

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。

生产者和消费者彼此之间不直接通讯,而通过这个容器来通讯,所以生产者生产完数据之后不用等待消费者处理,直接将生产的数据放到这个容器当中,消费者也不用找生产者要数据,而是直接从这个容器里取数据,这个容器就相当于一个缓冲区,平衡了生产者和消费者的处理能力,这个容器实际上就是用来给生产者和消费者解耦的。

在这里插入图片描述
生产者消费者模型的特点

生产者消费者模型是多线程同步与互斥的一个经典场景,其特点如下:

  • 三种关系: 生产者和生产者(互斥关系)、消费者和消费者(互斥关系)、生产者和消费者(互斥关系、同步关系)。
  • 两种角色: 生产者和消费者。(通常由进程或线程承担)
  • 一个交易场所: 通常指的是内存中的一段缓冲区。(可以自己通过某种方式组织起来)

生产者和生产者、消费者和消费者、生产者和消费者,它们之间为什么会存在互斥关系?

因为在生产者和消费者之间存在多种执行流同时访问的问题,,因此我们需要将他们同时访问的临界区进行加互斥保护起来
其中,所有的生产者和消费者都会竞争式的申请锁,因此生产者和生产者、消费者和消费者、生产者和消费者之间都存在互斥关系。

生产者和消费者之间为什么会存在同步关系?

  • 如果让生产者一直生产,那么当生产者生产的数据将容器塞满后,生产者再生产数据就会生产失败。
  • 反之,让消费者一直消费,那么当容器当中的数据被消费完后,消费者再进行消费就会消费失败。

虽然这样不会造成任何数据不一致的问题,但是这样会引起另一方的饥饿问题,是非常低效的。我们应该让生产者和消费者访问该容器时具有一定的顺序性,比如让生产者先生产,然后再让消费者进行消费。

  • 注意: 互斥关系保证的是数据的正确性,而同步关系是为了让多线程之间协同起来。

基于BlockingQueue的生产者消费者模型

当多个生产者,消费者同时出现进行抢占线程时,我们可以使用BlockingQueue来进行缓冲,如图

在这里插入图片描述
其与普通的队列的区别在于:

  • 当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中放入了元素。
  • 当队列满时,往队列里存放元素的操作会被阻塞,直到有元素从队列中取出。

知识联系: 看到以上阻塞队列的描述,我们很容易想到的就是管道,而阻塞队列最典型的应用场景实际上就是管道的实现。

在这里插入图片描述
put为生产者,take为消费者

全部代码

task.hpp 用于实现打印和计算

#pragma once
#include <iostream>
#include <string>

class Task
{
public:
    Task(){}
    Task(int x,int y,char op):_x(x),_y(y),_op(op),_result(0),_exitCode(0)
    {}

    void operator()()
    {
        switch(_op)
        {
            case '+' :
                _result = _x + _y;
                break;
            case '-':
            _result = _x - _y;
            break;
        case '*':
            _result = _x * _y;
            break;
        case '/':
        {
            if (_y == 0)
                _exitCode = -1;
            else
                _result = _x / _y;
        }
        break;
        case '%':
        {
            if (_y == 0)
                _exitCode = -2;
            else
                _result = _x % _y;
        }
        break;

        default:
            break;
        }
    }
    std::string formatArg()
    {
        return std::to_string(_x) + _op + std::to_string(_y) + "="; 
    }

    std::string formatRes()
    {
        return std::to_string(_result) + "(" + std::to_string(_exitCode) + ")";
    }

    ~Task()
    {}
private:
    int _x;
    int _y;
    char _op;//输入的符号

    int _result;
    int _exitCode;
};

blockQueue.hpp 维护线程之间的同步

#pragma once 
#include <iostream>
#include <queue>
#include <pthread.h>

const int gcap = 5;//最大容量
template<class T>
class BlockQueue
{
public:
    BlockQueue(const int cap = gcap):_cap(gcap)
    {
        pthread_mutex_init(&_mutex,nullptr);//初始化互斥量

        //初始化用户和生产者的条件变量
        pthread_cond_init(&_consumerCond,nullptr);
        pthread_cond_init(&_consumerCond,nullptr);
    }


    //判断是否为慢
    bool isFull()
    {
        return _q.size() == _cap;
    }

    //判断是否为空
    bool isEmpty()
    {
        return _q.empty();
    }

    //插入
    void push(const T &in)
    {
        pthread_mutex_lock(&_mutex);
        //细节1:一定要保证,在任何时候,都要符合条件,才进行生产
        while(isFull())
        {
            //1 我们只能在临界区内部,判断临界区资源是否就绪!注定了我们在当前一定持有锁。
            //2 要让线程进行休眠等待,不能持有锁等待
            //3 注定了pthread_cond_wait要有锁的释放能力
            pthread_cond_wait(&_productCond,&_mutex);
            // 4. 当线程醒来的时候,注定了继续从临界区内部继续运行!因为我是在临界区被切走的!
            // 5. 注定了当线程被唤醒的时候,继续在pthread_cond_wait函数出向后运行,又要重新申请锁,申请成功才会彻底返回
        }

        // 没有满,就要让他继续运行
        _q.push(in);

        //加策略
        pthread_cond_signal(&_consumerCond);
        pthread_mutex_unlock(&_mutex);
    }

    //取出删除
    void pop(T* out)
    {
        pthread_mutex_lock(&_mutex);
        while(isEmpty()) 
        {
            pthread_cond_wait(&_consumerCond, &_mutex);
        }
        *out = _q.front();
        _q.pop();
        // 加策略
        pthread_cond_signal(&_productCond);
        pthread_mutex_unlock(&_mutex);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_consumerCond);
        pthread_cond_destroy(&_productCond);
    }
private:
    std::queue<T> _q;
    int _cap;//生产容量
    //为什么我们这份代码只有一个锁,根本原因在于
    //我们生产者和消费者访问的是同一个queue && queue 被当作整体使用
    pthread_mutex_t _mutex;
    pthread_cond_t _consumerCond;//消费者对应的条件变量
    pthread_cond_t _productCond;//生产者对应的条件变量
};

main.cc

#include <iostream>
#include "task.hpp"
#include "blockQueue.hpp"
#include <pthread.h>
#include <ctime>
#include <unistd.h>

void* consumer(void* args)
{
    BlockQueue<Task>* bq = static_cast<BlockQueue<Task>*>(args);

    while(1)
    {
        Task t;
        // 1. 将数据从blockqueue中获取 -- 获取到了数据
        bq->pop(&t);
        t();
        // 2. 结合某种业务逻辑,处理数据! -- TODO
        std::cout << pthread_self() << " | consumer data: " << t.formatArg() << t.formatRes() << std::endl;
    }
}

void *productor(void *args)
{
    BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);
    std::string opers = "+-*/%";
    while(1)
    {
        int x = rand()%20 +1;
        int y = rand()%20 +1;

        char op = opers[rand() % opers.size()];

        Task t(x,y,op);
        bq->push(t);
        std::cout << pthread_self() << " | productor Task: " <<  t.formatArg() << "?" << std::endl;
    }
}

int main()
{
    BlockQueue<Task>*bq = new BlockQueue<Task>();
    pthread_t c[2], p[3];
    pthread_create(&c[0], nullptr, consumer, bq);
    pthread_create(&c[1], nullptr, consumer, bq);
    pthread_create(&p[0], nullptr, productor, bq);
    pthread_create(&p[1], nullptr, productor, bq);
    pthread_create(&p[2], nullptr, productor, bq);

    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);
    delete bq;
    return 0;
    return 0;
}

在这里插入图片描述

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

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

相关文章

网安工具 | Windows便携式渗透测试环境PentestBox入门到进阶使用指南

微信改版了&#xff0c;现在看到我们全凭缘分&#xff0c;为了不错过【全栈工程师修炼指南】重要内容及福利&#xff0c;大家记得按照上方步骤设置「接收文章推送」哦~ 关注【公众号】回复【学习交流群】加入【SecDevOps】学习交流群! 文章目录&#xff1a; 本文为作者原创文章…

Kafka Log存储解析以及索引机制

1.概述 在Kafka架构&#xff0c;不管是生产者Producer还是消费者Consumer面向的都是Topic。Topic是逻辑上的概念&#xff0c;而Partition是物理上的概念。每个Partition逻辑上对应一个log文件&#xff0c;该log文件存储是Producer生产的数据。Producer生产的数据被不断追加到该…

CSP-J第二轮试题-2021/2/3年-注意事项

参考: https://blog.csdn.net/Keven_11/article/details/126388791 https://www.cnblogs.com/-Wallace-/p/cpp-stl.html 2021年真题要求 2022年真题要求 考试注意事项 优先注意&#xff1a; 1.使用freopen。 #include <bits/stdc.h> #include<cstdio>//必须包含…

晨控CK-FR08系列读写器与LS可编程逻辑控制器MODBUSRTU连接手册

晨控CK-FR08系列读写器与LS可编程逻辑控制器MODBUSRTU连接手册 晨控CK-FR08是一款基于射频识别技术的高频RFID标签读卡器&#xff0c;读卡器工作频率为13.56MHZ&#xff0c;支持对I-CODE 2、I-CODE SLI等符合ISO15693国际标准协议格式标签的读取。读卡器内部集成了射频部分通信…

spring-boot入门之如何利用idea创建一个spring-boot项目

1.创建流程&#xff01;&#xff01;&#xff01; 选择新建项目&#xff0c;这里我们需要注意是基于maven建立的和java版本和jdk版本要对应 这里我们是基于web项目创建的记得选择这个框架。 2.测试程序 编写hello测试类 我们需要通过程序的入口进行启动程序。idea已经为我们自…

【算法练习Day7】反转字符串替换空格反转字符串中的单词左旋转字符串

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 反转字符串反转字符串 I…

【洛谷 P1644】跳马问题 题解(深度优先搜索)

跳马问题 题目背景 在爱与愁的故事第一弹第三章出来前先练练四道基本的回溯/搜索题吧…… 题目描述 中国象棋半张棋盘如图 1 1 1 所示。马自左下角 ( 0 , 0 ) (0,0) (0,0) 向右上角 ( m , n ) (m,n) (m,n) 跳。规定只能往右跳&#xff0c;不准往左跳。比如图 1 1 1 中所…

修改switch Nand无线区码 以支持高频5G 信道

环境&#xff1a;NS switch 问题&#xff1a;日版&#xff0c;港版无法连接大于44信道的5G WIFI 解决办法&#xff1a;修改PRODINFO.dec的WIFI 区域码 背景&#xff1a;我的switch是最早买的港版的一批&#xff0c;WIFI 只能连接日本的信道&#xff0c;家里的路由器是大陆的&am…

CentOS7安装Oracle XE记录

本文仅是CentOS7安装Oracle XE记录&#xff0c;供参考 1、下载安装包 oracle-xe-11.2.0-1.0.x86_64.rpm.zip 2、安装 &#xff08;1&#xff09;第一次安装 [rootnode1 opt]# cd oracle-xe/ [rootnode1 oracle-xe]# ll 总用量 309884 -rw-r--r-- 1 root root 317320273 6月…

软件设计模式系列之十七——解释器模式

1 模式的定义 解释器模式是一种行为型设计模式&#xff0c;它用于将一种语言或表达式解释为对象。该模式通过定义语言的文法规则&#xff0c;并使用解释器来解释和执行这些规则&#xff0c;将复杂的语言转换为对象的操作。 在软件开发中&#xff0c;解释器模式常用于处理类似…

初识ebpf

介绍eBPF技术 当代计算机系统中&#xff0c;性能、安全性和可观察性是至关重要的关键因素。为了应对这些挑战&#xff0c;Linux 内核引入了一种名为eBPF&#xff08;extended Berkeley Packet Filter&#xff09;的强大技术。eBPF 不仅仅是一种网络数据包过滤器&#xff0c;它…

轻松拿下Offer!20个Salesforce管理员顾问的基础面试问题

先是Salesforce第二财季业绩远超预期&#xff0c;股价大涨&#xff0c;后是首席执行官Marc Benioff表示将在各部门招聘3300名员工&#xff0c;生态系统呈现欣欣向荣的态势&#xff0c;Salesforce也成为越来越多人的职业选择。 管理员作为入门级的岗位&#xff0c;是小白和新手…

黑豹程序员-再不怕猪队友把配置文件上传git暴露数据库密码了

问题&#xff1a; 项目中含有配置文件&#xff0c;而配置文件中含有数据库的用户名和密码。而团队猪队友不论三七二十一玩git时全都上传git。git上开放给外部用户。外部用户获得数据库ip地址&#xff0c;用户名&#xff0c;密码。如果运维猪队友数据库为方便直接对外网开放。那…

CSS笔记——Display属性元素分类(行内、块,行内块)CSS默认样式及解决方案

1、display属性 ​ 可取值&#xff1a; ​ none 影藏&#xff08; 自身及其后代&#xff0c;一般用block值恢复&#xff09; ​ block 块元素 ​ inline 行内元素 ​ inline-block 行内块元素 ​ list-item (l列表元素的display值&#xff0c;实际效果比块元素多了列表的项目符…

洗地机性价比高的是哪款?高性价比洗地机排名

洗地机已成为当下备受欢迎的智能家电之一&#xff0c;但在挑选合适的洗地机时&#xff0c;面对各种新词汇和功能选择&#xff0c;可能会让人感到困惑。因此&#xff0c;为了帮助大家在购买洗地机时不踩坑&#xff0c;我们基于市面上主流品牌的综合分析对比&#xff0c;总结出来…

C++项目笔记--基于TensorRT搭建一个YoloV5服务器

目录 1--项目描述 2--项目地址 3--编译运行 4--测试结果 5--补充说明 1--项目描述 ① 基于 C/S 模型来构建 TCP 服务器和 TCP 客户端。 ② 使用 Epoll 来监控服务器和客户端之间的连接。 ③ 服务器和客户端约定使用相同的数据传输协议&#xff0c;头部分别使用 4 个字节来…

Windows下配置MySQL源码调试环境

Windows下配置MySQL源码调试环境 环境准备编译安装MySQL DeBug版MySQL初始化安装VS Code插件调试 参考链接&#xff1a; https://zhuanlan.zhihu.com/p/651665372 https://zhuanlan.zhihu.com/p/606732848 环境准备 # 创建存放源码的根目录 mkdir -p /root/code# 进入存放源码…

凹凸贴图和法线贴图的渲染效果对比

1、什么是凹凸贴图 凹凸贴图&#xff08;bump mapping&#xff09;是一种计算机图形学中的渲染技术&#xff0c;用于在给定的表面上模拟微小的凹凸纹理。通过在表面法线方向上微调每个像素的光照值&#xff0c;可以给平滑的表面增加视觉上的凹凸感。 在凹凸贴图中&#xff0c;每…

“押宝高手”乐视视频再出手,看中商业传奇剧《大盛魁》

作为最早开始版权采购的长视频平台&#xff0c;乐视视频一向擅长“押宝”优质内容。从《甄嬛传》到《白鹿原》等&#xff0c;乐视拿下了众多经典古装剧、年代剧的版权。 9月&#xff0c;乐视视频再次出手拿下的历史传奇剧《大盛魁》开始热播。该剧由王新民导演执导&#xff0c…

如何开始着手一篇Meta分析 | Meta分析的流程及方法

Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;最早出现于“循证医学”&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面。…