linux:生产者消费者模型

在这里插入图片描述

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》《C++》《Linux》

文章目录

  • 前言
  • 一、生产者消费者模型
  • 二、基于阻塞队列的生产者消费者模型
    • 代码实现
  • 总结


前言

本文是对于生产者消费者模型的知识总结


一、生产者消费者模型

生产者消费者模型就是通过一个容器来解决生产者消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而是通过之间的容器来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接交给容器,消费者不找生产者要数据,而是直接从容器中取数据,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力,其中这个容器就是用于生产者和消费者之间解耦的
(强耦合是指两个或多个系统,组件或模块之间存在紧密依赖关系。)

在这里插入图片描述


特点:

  1. 三种关系:生产者与生产者之间(互斥),消费者与消费者之间(互斥),生产者与消费者之间(互斥 && 同步)
  2. 两种角色:生产者和消费者
  3. 一个交易(通讯)场所:一个容器(一段内存空间)

因为我们是多个线程访问同一个容器,那必然会导致数据不一致的问题,所以我们需要对该临界资源加锁,所以生产者与生产者之间,消费者与消费者之间,生产者与消费者之间都是互斥的。
又因为容器可能为空(满),此时消费者(生产者)还一直在临界区申请锁,又因没有数据(空间)而释放锁,从而不断申请锁释放锁,导致生产者(消费者)的饥饿问题。此时我们就需要生产者与消费者之间的同步。

对于2,3两点,这很好理解不解释。
我们编写生产者消费者模型的本质就是对以上三点的维护。(互斥保证数据安全,同步保证效率)


优点:

  1. 解耦
  2. 支持并发
  3. 支持忙闲不均

对于第一点,不就是生产者与消费者通过容器来解耦提升效率(如果没有这个容器,则生产者生产完数据,就必须等待消费者来接受处理数据,不能立刻继续生产数据)。
对于第二点,当生产者在生产数据时,消费者也同时在处理数据
对于第三点,当生产者生产数据的速度超过消费者的处理能力时,容器可以起到缓存的作用,将多余的数据暂时存储,等待消费者有空闲时再进行处理。如果消费者处理数据的能力超过生产者时,同理。

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

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。

  • 队列为空时,从队列中取数据将被阻塞,直到队列中有数据时被唤醒。
  • 队列为满时,向队列中放入数据将被阻塞,直到队列中有数据取出被唤醒。

代码实现

下面是一个单生成单消费模型
在这里插入图片描述
LockGuard.hpp 文件 将加锁释放锁,交给一个对象处理,当对象创建加锁,对象销毁释放锁

#pragma once
#include <pthread.h>class Mutex
{
public:Mutex(pthread_mutex_t *mutex):_mutex(mutex){}void Lock(){pthread_mutex_lock(_mutex);}void UnLock(){pthread_mutex_unlock(_mutex);}~Mutex(){}
private:pthread_mutex_t *_mutex;
};class LockGuard
{
public:LockGuard(pthread_mutex_t *mutex): _lock(mutex){_lock.Lock();}~LockGuard(){_lock.UnLock();}
private:Mutex _lock;
};

Blockqueue.hpp 文件

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
#include "LockGuard.hpp"using namespace std;
const int CAPACITY = 5;template<class T>
class BlockQueue
{
public:BlockQueue(int cap = CAPACITY):_capacity(cap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_p, nullptr);pthread_cond_init(&_c, nullptr);}bool isFull(){return _bq.size() == _capacity;}void Push(const T &in){LockGuard mutex(&_mutex);//pthread_mutex_lock(&_mutex);while(isFull()){pthread_cond_wait(&_p, &_mutex);}_bq.push(in);// 唤醒策略为 生产一个,消费一个pthread_cond_signal(&_c);//pthread_mutex_unlock(&_mutex);}bool isEmpty(){return _bq.size() == 0;}void Pop(T *out){LockGuard mutex(&_mutex);//pthread_mutex_lock(&_mutex);while(isEmpty()){pthread_cond_wait(&_c, &_mutex);}*out = _bq.front();_bq.pop();// 唤醒策略为 消费一个,生产一个pthread_cond_signal(&_p);//pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_p);pthread_cond_destroy(&_c);}
private:queue<T> _bq;int _capacity;pthread_mutex_t _mutex;pthread_cond_t _p;pthread_cond_t _c;
};

Task.hpp 文件

#pragma once
#include <string>const char *opers = "+-*/%";enum
{ok = 0,div_zero,mod_zero
};class Task
{
public:Task(){}Task(int x, int y, char op) : _data_x(x), _data_y(y), _oper(op){_code = ok;}void Run(){switch (_oper){case '+':_result = _data_x + _data_y;break;case '-':_result = _data_x - _data_y;break;case '*':_result = _data_x * _data_y;break;case '/':{if(_data_y == 0){_code = div_zero;}else{_result = _data_x / _data_y;}}break;case '%':{if(_data_y == 0){_code = mod_zero;}else{_result = _data_x % _data_y;}}break;default:break;}}void operator()(){Run();}std::string PrintTask(){std::string ret = std::to_string(_data_x);ret += _oper;ret += std::to_string(_data_y);ret += "=?";return ret;}std::string PrintResult(){std::string ret = std::to_string(_data_x);ret += _oper;ret += std::to_string(_data_y);ret += "=";if(_code == ok){ret += std::to_string(_result);}else{ret += "?";}ret += "[";ret += std::to_string(_code);ret += "]";return ret;}~Task(){}private:int _data_x;int _data_y;char _oper;int _result;int _code; // 错误码
};

Main.cc 文件

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <ctime>
#include <string.h>
#include "BlockQueue.hpp"
#include "Task.hpp"
using namespace std;void *producer(void *args)
{BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);// 产生任务while (true){int x = rand() % 10 + 1;int y = rand() % 10 + 1;char oper = opers[rand() % strlen(opers)];Task task(x, y, oper);cout << "producer: " << task.PrintTask() << endl;bq->Push(task);sleep(1);}return nullptr;
}void *consumer(void *args)
{// usleep(1000);BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);// 获取任务,处理任务while (true){if (bq->isFull()){Task task;bq->Pop(&task);task();cout << "consumer: " << task.PrintResult() << endl;//sleep(1);}}
}int main()
{srand(time(nullptr) ^ getpid());BlockQueue<Task> bq;pthread_t p;pthread_create(&p, nullptr, producer, (void *)&bq);pthread_t c;pthread_create(&c, nullptr, consumer, (void *)&bq);pthread_join(p, nullptr);pthread_join(c, nullptr);return 0;
}

在这里插入图片描述
在这里插入图片描述

那如何将这个单生产单消费该为多生产多消费呢?因为多生产多消费本质也是多个线程访问临界资源,那我们单生产和单消费不也是多个线程访问临界资源吗,所以我们不需要对BlockQueue.hpp文件进行修改,只需要在main函数中,创建多个生产者和消费者即可。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <ctime>
#include <string.h>
#include "BlockQueue.hpp"
#include "Task.hpp"
using namespace std;template <class T>
class ThreadData
{
public:ThreadData(pthread_t tid, const string threadname, BlockQueue<T> *bq): _tid(tid), _threadname(threadname), _bq(bq){}public:pthread_t _tid;string _threadname;BlockQueue<T>* _bq;
};void *producer(void *args)
{ThreadData<Task> *data = static_cast<ThreadData<Task> *>(args);// 产生任务while (true){int x = rand() % 10 + 1;int y = rand() % 10 + 1;char oper = opers[rand() % strlen(opers)];Task task(x, y, oper);cout << data->_tid << ", " << data->_threadname <<": " << task.PrintTask() << endl;data->_bq->Push(task);sleep(1);}return nullptr;
}void *consumer(void *args)
{// usleep(1000);ThreadData<Task> *data = static_cast<ThreadData<Task> *>(args);// 获取任务,处理任务while (true){if (data->_bq->isFull()){Task task;data->_bq->Pop(&task);task();cout << data->_tid << ", " << data->_threadname << ": " << task.PrintResult() << endl;// sleep(1);}}
}int main()
{srand(time(nullptr) ^ getpid());BlockQueue<Task> bq;pthread_t p1;ThreadData<Task> data1(p1, "product-1", &bq);pthread_create(&p1, nullptr, producer, (void *)&data1);pthread_t p2;ThreadData<Task> data2(p2, "product-2", &bq);pthread_create(&p2, nullptr, producer, (void *)&data2);pthread_t c1;ThreadData<Task> data3(c1, "consumer-1", &bq);pthread_create(&c1, nullptr, consumer, (void *)&data3);pthread_t c2;ThreadData<Task> data4(c2, "consumer-2", &bq);pthread_create(&c2, nullptr, consumer, (void *)&data4);pthread_join(p1, nullptr);pthread_join(p2, nullptr);pthread_join(c1, nullptr);pthread_join(c2, nullptr);return 0;
}

在这里插入图片描述
在这里插入图片描述


总结

以上就是我对于线程同步的总结。

在这里插入图片描述

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

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

相关文章

Mac电脑Jmeter集成到Jenkins,压测多个接口并生成测试报告

Jenkins支持的JDK版本17、21&#xff0c;通过java -version查看当前JDK版本&#xff0c;确认是否匹配 打开网址https://www.jenkins.io/download 点击下载&#xff0c;选择mac版本 commend空格打开终端&#xff0c;输入安装命令brew install jenkins 安装完成后输入brew servi…

2021-08-06

yarn的简介&#xff1a; Yarn是facebook发布的一款取代npm的包管理工具。 yarn的特点&#xff1a; 速度超快。 Yarn 缓存了每个下载过的包&#xff0c;所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率&#xff0c;因此安装速度更快。超级安全。 在执行代码…

AI绘图cuda与stable diffusion安装部署始末与避坑

stable diffusion的安装说起来很讽刺&#xff0c;最难的不是stable diffusion&#xff0c;而是下载安装cuda。下来我就来分享一下我的安装过程&#xff0c;失败了好几次&#xff0c;几近放弃。 一、安装cuda 我们都知道cuda是显卡CPU工作的驱动&#xff08;或者安装官网的解释…

Stable Diffusion WebUI 附加功能/图片放大(Extras):单张图片/批量处理/从目录进行批量处理

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里&#xff0c;订阅后可阅读专栏内所有文章。 大家好&#xff0c;我是水滴~~ 篇文章主要讲解 Stable Diffusion WebUI 的附加功能/图片放大&#xff08;Extras&#xff09;的使用&#xff0c;主要…

计算机网络——33多点访问协议

多点访问协议 多路访问链路和协议 两种类型的链路&#xff08;一个子网内部链路连接形式&#xff09; 点对点 拨号访问的PPP以太网交换机和主机之间的点对点链路 广播 传统以太网HFC上行链路802.11无线局域网 多路访问协议 单个共享的广播型链路 2个过更多结点同时传送&am…

数据结构——二叉树——二叉搜索树(Binary Search Tree, BST)

目录 一、98. 验证二叉搜索树 二、96. 不同的二叉搜索树 三、538. 把二叉搜索树转换为累加树 二叉搜索树&#xff1a;对于二叉搜索树中的每个结点&#xff0c;其左子结点的值小于该结点的值&#xff0c;而右子结点的值大于该结点的值 一、98. 验证二叉搜索树 给你一个二叉树的…

uni app 扫雷

闲来无聊。做个扫雷玩玩吧&#xff0c;点击打开&#xff0c;长按标记&#xff0c;标记的点击两次或长按取消标记。所有打开结束 <template><view class"page_main"><view class"add_button" style"width: 100vw; margin-bottom: 20r…

深入了解 Vue 3 中的 Transition 过渡动画

在本文中&#xff0c;我们将深入探讨 Vue 3 中实现 Transition 过渡动画的技术细节。过渡动画可以为用户界面增添平滑和生动的效果&#xff0c;提升用户体验。 首先新建一个基于uni-app框架为transition.vue的测试文件&#xff0c;在其中编写如下JavaScript、HTML和CSS代码&…

CSS3 Transform变形理解与应用

Transform&#xff1a;对元素进行变形&#xff1b; Transition&#xff1a;对元素某个属性或多个属性的变化&#xff0c;进行控制&#xff08;时间等&#xff09;&#xff0c;类似flash的补间动画。但只有两个关键贞。开始&#xff0c;结束。 Animation&#xff1a;对元素某个属…

vulhub中Apache solr XML 实体注入漏洞复现(CVE-2017-12629)

Apache Solr 是一个开源的搜索服务器。Solr 使用 Java 语言开发&#xff0c;主要基于 HTTP 和 Apache Lucene 实现。原理大致是文档通过Http利用XML加到一个搜索集合中。查询该集合也是通过 http收到一个XML/JSON响应来实现。此次7.1.0之前版本总共爆出两个漏洞&#xff1a;XML…

智慧安防监控EasyCVR视频调阅和设备录像回看无法自动播放的原因排查与解决

智慧安防监控EasyCVR视频管理平台能在复杂的网络环境中&#xff0c;将前端设备统一集中接入与汇聚管理。国标GB28181协议视频监控/视频汇聚EasyCVR平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、…

云计算迎变局:阿里云、腾讯云“各有千秋”

毋庸置疑&#xff0c;无论在什么时候什么行业&#xff0c;低价策略都是一柄利器。比如&#xff0c;在电商行业&#xff0c;除了拼多多将低价策略贯彻到底之外&#xff0c;淘宝、京东也将性价比作为发力重点&#xff0c;并通过补贴、秒杀等方式&#xff0c;再度强调自身的“价格…

目标检测——工业安全生产环境违规使用手机的识别

一、重要性及意义 首先&#xff0c;工业安全生产环境涉及到许多复杂的工艺和设备&#xff0c;这些设备和工艺往往需要高精度的操作和严格的监管。如果员工在生产过程中违规使用手机&#xff0c;不仅可能分散其注意力&#xff0c;降低工作效率&#xff0c;更可能因操作失误导致…

TCP/IP 网络模型有哪几层?(计算机网络)

应用层 为用户提供应用功能 传输层 负责为应用层提供网络支持 使用TCP和UDP 当传输层的数据包大小超过 MSS&#xff08;TCP 最大报文段长度&#xff09; &#xff0c;就要将数据包分块&#xff0c;这样即使中途有一个分块丢失或损坏了&#xff0c;只需要重新发送这一个分块…

JavaEE SSM框架学习——MacOS Eclipse环境搭建

MacOS环境搭建 安装Homebrew Homebrew是一个包管理器&#xff0c;我们可以通过它来安装许多软件 首先打开Homebrew中文官网(brew.sh/zh-cn) 如图所示&#xff0c;复制下面那行命令到你的Macbook终端 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ho…

商场促销--策略模式

1.1 商场收银软件 package com.lhx.design.pattern.test;import java.util.Scanner;public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式…

npm ERR! code CERT_HAS_EXPIRED 淘宝镜像失效

近期vue安装失败&#xff0c;具体如下&#xff1a; 1.先npm cache clean --force 再下载 插件后缀加上 --legacy-peer-deps 2.certificate has expired npm ERR! code CERT_HAS_EXPIRED npm ERR! errno CERT_HAS_EXPIRED npm ERR! request to https://registry.npm.taobao.o…

预处理指令——一些比较少见的概念

前言&#xff1a;预处理是我们的c语言源代码成为可执行程序的第一个步骤。而宏和预处理指令都是在这个阶段完成。本节内容就是关于宏和预处理指令相关知识点的解析。 目录 宏 预定义符号 #define定义常量 #define定义符号 #define定义宏 带副作用的宏参数 宏的替换规则…

SMTP服务器搭建关键步骤?如何配置服务器?

SMTP服务器搭建的注意事项&#xff1f;怎么快速搭建SMTP服务器&#xff1f; 电子邮件已经成为我们日常工作和生活中不可或缺的一部分。SMTP服务器作为电子邮件发送的核心组件&#xff0c;其搭建过程至关重要。下面&#xff0c;AokSend就来详细探讨一下SMTP服务器搭建的关键步骤…

web学习笔记(五十)

目录 1. nodemon 1.1 什么是nodemon 1.2 安装并使用Nodemon 2. Express 路由 2.1 路由的匹配过程 2.2 简单路由 2.3 模块化路由 2.4 注册路由模块 2.5 路由模块添加前缀 3. Express 中间件 3.1 中间件的格式 3.2 中间件的作用 3.3 局部生效的中间件 3.4 中间件…