linux:生产者消费者模型
个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》《C++》《Linux》
文章目录
- 前言
- 一、生产者消费者模型
- 二、基于阻塞队列的生产者消费者模型
- 代码实现
- 总结
前言
本文是对于生产者消费者模型的知识总结
一、生产者消费者模型
生产者消费者模型就是通过一个容器来解决生产者消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而是通过之间的容器来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接交给容器,消费者不找生产者要数据,而是直接从容器中取数据,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力,其中这个容器就是用于生产者和消费者之间解耦的
(强耦合是指两个或多个系统,组件或模块之间存在紧密依赖关系。)
特点:
- 三种关系:生产者与生产者之间(互斥),消费者与消费者之间(互斥),生产者与消费者之间(互斥 && 同步)
- 两种角色:生产者和消费者
- 一个交易(通讯)场所:一个容器(一段内存空间)
因为我们是多个线程访问同一个容器,那必然会导致数据不一致的问题,所以我们需要对该临界资源加锁,所以生产者与生产者之间,消费者与消费者之间,生产者与消费者之间都是互斥的。
又因为容器可能为空(满),此时消费者(生产者)还一直在临界区申请锁,又因没有数据(空间)而释放锁,从而不断申请锁释放锁,导致生产者(消费者)的饥饿问题。此时我们就需要生产者与消费者之间的同步。
对于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;
}
总结
以上就是我对于线程同步的总结。
相关文章:

linux:生产者消费者模型
个人主页 : 个人主页 个人专栏 : 《数据结构》 《C语言》《C》《Linux》 文章目录 前言一、生产者消费者模型二、基于阻塞队列的生产者消费者模型代码实现 总结 前言 本文是对于生产者消费者模型的知识总结 一、生产者消费者模型 生产者消费者模型就是…...

C++教学——从入门到精通 5.单精度实数float
众所周知,三角形的面积公式是(底*高)/2 那就来做个三角形面积计算器吧 到吗如下 #include"bits/stdc.h" using namespace std; int main(){int a,b;cin>>a>>b;cout<<(a*b)/2; } 这不对呀,明明是7.5而他却是7,…...
面向对象设计之单一职责原则
设计模式专栏:http://t.csdnimg.cn/6sBRl 目录 1.单一职责原则的定义和解读 2.如何判断类的职责是否单一 3.类的职责是否越细化越好 4.总结 1.单一职责原则的定义和解读 单一职责原则(Single Responsibility Principle,SRP)的描述:一个类…...

蓝桥杯真题:单词分析
import java.util.Scanner; //1:无需package //2: 类名必须Main, 不可修改 public class Main{public static void main(String[]args) {Scanner sannernew Scanner(System.in);String strsanner.nextLine();int []anew int [26];for(int i0;i<str.length();i) {a[str.charA…...

Python字符串字母大小写变换,高级Python开发技术
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书! ‘’’ demo ‘tHis iS a GOod boOK.’ print(demo.casefold()) print(demo.lower()) print(demo.upper()) print(demo.capitalize()) print(demo.title()) print(dem…...
CentOS常用功能命令集合
1、删除指定目录下所有的空目录 find /xxx -type d -empty -exec rmdir {} 2、删除指定目录下近7天之前的日志文件 find /xxx -name "*.log" -type f -mtime 7 -exec rm -f {} \; 3、查询指定目录下所有的指定格式文件(比如PDF文件) find…...

黑马点评项目笔记 II
基于Stream的消息队列 stream是一种数据类型,可以实现一个功能非常完善的消息队列 key:队列名称 nomkstream:如果队列不存在是否自动创建,默认创建 maxlen/minid:设置消息队列的最大消息数量 *|ID 唯一id:…...
关于一篇知乎答案的重现
〇、前言 早上在逛知乎的时候,瞥见了一篇答案:如何通俗解释Docker是什么?感觉很不错,然后就耐着性子看了下,并重现了作者的整个过程。但是并不顺利,记载一下这些坑。嫌麻烦的话可以直接clone 研究…...
实时数据库测试-汇编小程序
实时数据库测试-汇编小程序。 hd.asm .686 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc include \masm32\include\gdi32.inc …...
HTML5 、CSS3 、ES6 新特性
HTML5 新特性 1. 新的语义化元素:article 、footer 、header 、nav 、section 2. 表单增强,新的表单控件:calendar 、date 、time 、email 、url 、search 3. 新的 API:音频(用于媒介回放的 video 和 audio 元素)、图形&#x…...

基于springboot+vue实现的驾校信息管理系统
作者主页:Java码库 主营内容:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】:Java 【框架】:spring…...
X进制减法(贪心算法C++实现)
题目 进制规定了数字在数位上逢几进一。 X 进制是一种很神奇的进制,因为其每一数位的进制并不固定! 例如说某种 X 进制数,最低数位为二进制,第二数位为十进制,第三数位为八进制,则 X 进制数 321 转换为十…...

[Windows]服务注册工具(nssm)
文章目录 官网下载地址百度云下载地址NSSM常用命令 使用场景:例如现在我们想开启自动启动一个Java服务,nginx,node等。 官网下载地址 https://nssm.cc/download 百度云下载地址 链接:https://pan.baidu.com/s/111fkBWIS7CTlWIj80Kc8Sg?pwdanan 提取码…...
Xilinx缓存使用说明和测试
Xilinx缓存使用说明和测试 1 BRAM说明2 FIFO说明3 实例测试3.1 代码3.2 仿真本文主要介绍Xilinx FPGA芯片中BRAM和FIFO的使用方法和测试结果,主要针对流接口进行仿真。 1 BRAM说明 BRAM是Xilinx芯片中重要的存储资源,其可配置为单端口RAM/ROM或者双端口RAM/ROM,本文以最复杂…...
LeetCode:2952. 需要添加的硬币的最小数量(贪心 Java)
目录 2952. 需要添加的硬币的最小数量 题目描述: 实现代码与解析: 贪心 原理思路: 2952. 需要添加的硬币的最小数量 题目描述: 给你一个下标从 0 开始的整数数组 coins,表示可用的硬币的面值,以及一个…...

企业员工在线培训系统功能介绍
随着信息技术的飞速发展,企业员工培训方式正逐步从传统的面授转向灵活高效的在线培训。一个综合性的企业员工在线培训系统能够为员工提供多样化的学习资源、便捷的学习途径和有效的学习监督,以下是该系统的主要功能详细介绍: 1. 课程功能 线…...
服了,一线城市的后端都卷成这样了吗!?
声明:本文首发在同名公众号:王中阳Go,未经授权禁止转载。 先听TA的故事 投稿的主人公是一名工作5年的后端开发工程师,最近2年用Golang,之前其他语言。去年春节前被裁员了,各种心酸史,好愁人啊。…...

Qt扫盲-QAssisant 集成其他qch帮助文档
QAssisant 集成其他qch帮助文档 一、概述二、Cmake qch例子1. 下载 Cmake.qch2. 添加qch1. 直接放置于Qt 帮助的目录下2. 在 QAssisant中添加 一、概述 QAssisant是一个很好的帮助文档,他提供了供我们在外部添加新的 qch帮助文档的功能接口,一般有两中添…...
[lesson01]学习C++的意义
学习C的意义 C语言特点 C语言是在实践的过程中逐步完善起来的 没有深思熟路的设计过程残留量过多低级语言的特征 C语言的目标是高效 最终程序执行效率的高效 软件方法论的发展 面相过程程序设计:数据结构 算法 主要解决科学计算问题,用户需求简单而…...

LabVIEW双通道太阳射电频谱观测系统
LabVIEW双通道太阳射电频谱观测系统 开发了一个基于LabVIEW平台开发的双通道高速太阳射电频谱观测系统。该系统实时监测太阳射电爆发,具有随机性、持续时间短、变化快等特点。通过高速信号采集卡实现1.5 GS/s的信号采集,时间分辨率可达4ms,频…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...