Linux多线程(十二)之【生产者消费者模型】

文章目录

    • 生产者消费者模型
      • 为何要使用生产者消费者模型
        • 生产者消费者模型优点
      • 基于BlockingQueue的生产者消费者模型
        • BlockingQueue
        • C++ queue模拟阻塞队列的生产消费模型
          • 单线程生产消费模型
          • 多线程生产消费模型

生产者消费者模型

consumer/productor

321原则

321原则(便于记忆)

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

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

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,

所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,

消费者不找生产者要数据,而是直接从阻塞队列里取,

阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

这个阻塞队列就是用来给生产者和消费者解耦的。

生产者消费者模型优点
  1. 解耦
  2. 支持并发
  3. 支持忙闲不均

image-20250425234123376

生产消费模型的高效问题

cp问题高效

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

BlockingQueue

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

其与普通的队列区别在于,

当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;

当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出

(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

image-20250425234454539

C++ queue模拟阻塞队列的生产消费模型

代码:

单线程生产消费模型

blockqueue.hpp

#pragma once#include <iostream>
#include <pthread.h>
#include <queue>using namespace std;template <class T>
class blockqueue
{static const int defaultnum = 20;public:blockqueue(int maxcap = defaultnum): maxcap_(maxcap){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&c_cond_, nullptr);pthread_cond_init(&p_cond_, nullptr);low_water=maxcap_/3;high_water=maxcap_*2/3;}~blockqueue(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&c_cond_);pthread_cond_destroy(&p_cond_);}T pop(){pthread_mutex_lock(&mutex_);if(q_.size()==0){pthread_cond_wait(&c_cond_,&mutex_);//1.调用的时候,自动释放锁}T t=q_.front();             // 你想消费,就直接能消费吗?不一定。你得先确保消费条件满足q_.pop();if(q_.size()<low_water)pthread_cond_signal(&p_cond_);pthread_mutex_unlock(&mutex_);return t;}void push(const T &in){pthread_mutex_lock(&mutex_);if(q_.size()==maxcap_){pthread_cond_wait(&p_cond_,&mutex_);//1.调用的时候,自动释放锁}q_.push(in);                // 你想生产,就直接能生产吗?不一定。你得先确保生产条件满足if(q_.size()>high_water)pthread_cond_signal(&c_cond_);pthread_mutex_unlock(&mutex_);}private:queue<T> q_;int maxcap_;// int mincap_;pthread_mutex_t mutex_;pthread_cond_t c_cond_;pthread_cond_t p_cond_;int low_water;int high_water;
};

main.cc

#include<iostream>
#include"blockqueue.hpp"
#include<unistd.h>using namespace std;void*Consumer(void*args)
{blockqueue<int> *bq=static_cast<blockqueue<int> *>(args);while(1){int data=bq->pop();cout<<"消费了一个数据: "<<data<<endl;// sleep(1);}
}void*Productor(void*args)
{blockqueue<int> *bq=static_cast<blockqueue<int> *>(args);int data=0;while(1){data++;bq->push(data);cout<<"生产了一个数据: "<<data<<endl;sleep(1);}
}int main()
{blockqueue<int> *bq=new blockqueue<int>();pthread_t c,p;pthread_create(&c,nullptr,Consumer,bq);pthread_create(&p,nullptr,Productor,bq);pthread_join(c,nullptr);pthread_join(p,nullptr);delete bq;return 0;
}

image-20250508225919230

消费者sleep

image-20250508230034577

两者都不sleep

image-20250508230244057

多线程生产消费模型

补充:

  1. 为什么判断条件要放到加锁之后?

因为判断临界资源条件是否满足,也是在访问临界资源!

判断临界资源是否就绪,是通过在临界区内部判断的!

  1. 如果临界资源未就绪,那么线程就要进行等待。

等待的时候,线程是持有锁的!所以调用wait时,自动释放锁。

如果不释放锁,直接等待,那么等待的线程就没有线程可以唤醒了,

因为其他线程都在锁外,进不去临界区。

该线程因为唤醒而返回的时候,重新持有锁了。

  1. 如果线程在wait时,被误唤醒了呢?

伪唤醒的概念

伪唤醒

解决方法:判断条件时用while,不用if

blockqueue.hpp

#pragma once#include <iostream>
#include <pthread.h>
#include <queue>using namespace std;template <class T>
class blockqueue
{static const int defaultnum = 20;public:blockqueue(int maxcap = defaultnum): maxcap_(maxcap){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&c_cond_, nullptr);pthread_cond_init(&p_cond_, nullptr);// low_water=maxcap_/3;// high_water=maxcap_*2/3;}~blockqueue(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&c_cond_);pthread_cond_destroy(&p_cond_);}T pop(){pthread_mutex_lock(&mutex_);while(q_.size()==0)//做到防止代码被伪唤醒!{pthread_cond_wait(&c_cond_,&mutex_);//1.调用的时候,自动释放锁}T t=q_.front();             // 你想消费,就直接能消费吗?不一定。你得先确保消费条件满足q_.pop();// if(q_.size()<low_water)pthread_cond_signal(&p_cond_);pthread_cond_signal(&p_cond_);//pthread_cond_broadcastpthread_mutex_unlock(&mutex_);return t;}void push(const T &in){pthread_mutex_lock(&mutex_);while(q_.size()==maxcap_)//做到防止代码被伪唤醒!{pthread_cond_wait(&p_cond_,&mutex_);//1.调用的时候,自动释放锁}q_.push(in);                // 你想生产,就直接能生产吗?不一定。你得先确保生产条件满足// if(q_.size()>high_water)pthread_cond_signal(&c_cond_);pthread_cond_signal(&c_cond_);pthread_mutex_unlock(&mutex_);}private:queue<T> q_;int maxcap_;// int mincap_;pthread_mutex_t mutex_;pthread_cond_t c_cond_;pthread_cond_t p_cond_;// int low_water;// int high_water;
};

Task.hpp

#pragma once
#include <iostream>
#include<string>
using namespace std;enum
{Div_zero = 1,Mod_zero,Unknown
};class Task
{
public:Task(int x, int y, char op): a(x), b(y), op_(op), ret(0), exitcode(0){}void Run(){switch (op_){case '+':ret = a + b;break;case '-':ret = a - b;break;case '*':ret = a * b;break;case '/':{if (b == 0)exitcode = Div_zero;elseret = a / b;}break;case '%':{if (b == 0)exitcode = Mod_zero;elseret = a % b;}break;default:exitcode=Unknown;break;}}string GetTask(){string r=to_string(a);r+=op_;r+=to_string(b);r+="=???";return r;}string Getret(){string r=to_string(a);r+=op_;r+=to_string(b);r+="=";r+=to_string(ret);r+=" [ exitcode: ";r+=to_string(exitcode);r+=" ]";return r;}void operator()(){Run();}~Task() {}private:int a;int b;char op_;int ret;int exitcode;
};

main.cc

#include <iostream>
#include "blockqueue.hpp"
#include <unistd.h>
#include "Task.hpp"
#include <ctime>using namespace std;string oper = "+-*/%";void *Consumer(void *args)
{blockqueue<Task> *bq = static_cast<blockqueue<Task> *>(args);while (1){Task data = bq->pop();// data.Run();// 计算// data.Run();data();cout << "处理了一个任务 , 运算结果为: " << data.Getret() << " ,thread id: " << pthread_self() << endl;// sleep(1);}
}void *Productor(void *args)
{int len = oper.size();blockqueue<Task> *bq = static_cast<blockqueue<Task> *>(args);// int x=0,y=0;// Task data(x,y);while (1){// 模拟生产者生产数据int x = rand() % 10 + 1; //[1,10]int y = rand() % 10;     //[0,9];char op = oper[rand() % len];Task data(x, y, op);usleep(10);// 计算bq->push(data);cout << "生产了一个任务: " << data.GetTask() << " ,thread id: " << pthread_self() << endl;sleep(1);}
}int main()
{srand(time(nullptr));blockqueue<Task> *bq = new blockqueue<Task>();pthread_t c[3], p[5];for (int i = 0; i < 3; i++){pthread_create(c + i, nullptr, Consumer, bq);}for (int i = 0; i < 5; i++){pthread_create(p + i, nullptr, Productor, bq);}for (int i = 0; i < 3; i++){pthread_join(c[i], nullptr);}for (int i = 0; i < 5; i++){pthread_join(p[i], nullptr);}delete bq;return 0;
}

image-20250511010553986

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

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

相关文章

MySQL表的操作(3)

文章目录前言一、创建表创建表时指定属性二、查看表查看表结构查看建表消息三、修改表修改列属性修改列名修改表名四、删除表总结前言 Hello! 那我们乘胜追击&#xff0c;开始 表的操作&#xff01; 一、创建表 首先创建一个 数据库 testForTable mysql> create database i…

从“人工智障”到“智能助手”:集成为什么能拯救AI用户体验?

几年前&#xff0c;当人们满怀期待地与AI语音助手对话时&#xff0c;常常遭遇令人啼笑皆非的回应——“抱歉&#xff0c;我不明白你在说什么”“请再说一遍”甚至答非所问。AI被戏称为“人工智障”&#xff0c;用户体验一度让人失望。然而&#xff0c;近年来&#xff0c;随着技…

Uniapp 自定义TabBar + 动态菜单实现教程(Vuex状态管理详解)

大家好&#xff0c;我是一诺。今天跟大家分享一下uniapp 封装自定义底部导航栏&#xff08;TabBar&#xff09; 过程中的思考和实践。通过本文&#xff0c;你将学会如何打造一个功能完善、可自由定制的TabBar组件&#xff01; 先看效果&#xff1a; 支持自定义图标和样式动态显…

MySQL数据库主从复制

概述1、master开启二进制日志记录2、slave开启IO进程&#xff0c;从master中读取二进制日志并写入slave的中继日志3、slave开启SQL进程&#xff0c;从中继日志中读取二进制日志并进行重放4、最终&#xff0c;达到slave与master中数据一致的状态&#xff0c;我们称作为主从复制的…

Rancher Server + Kubernets搭建云原生集群平台

目录Rancher Server Kubernets搭建云原生集群平台一、环境准备1、软件准备2、环境规划3、挂载数据盘二、虚拟机初始化基础配置&#xff08;所有节点都需要操作&#xff09;1、执行时间服务器脚本&#xff08;包括配置hostName主机名&#xff09;2、配置hosts文件3、配置各节点…

Java学习第八部分——泛型

目录 一、概述 &#xff08;一&#xff09;定义 &#xff08;二&#xff09;作用 &#xff08;三&#xff09;引入原因 二、使用 &#xff08;一&#xff09;类 &#xff08;二&#xff09;接口 &#xff08;三&#xff09;方法 三、类型参数 &#xff08;一&#xf…

定时点击二次鼠标 定时点击鼠标

定时点击二次鼠标 定时点击鼠标 今天分享一个定时点击两次的小工具。 我们在生活中&#xff0c;可能会遇到一些定时点击的任务。比如说在晚上9点去发送一个群发&#xff0c;或者倒计时点击一个按钮。那么可以使用这个工具&#xff0c;仅适用于Windows电脑。 #定时点击鼠标 #倒计…

Linux网络配置与故障排除完全指南

1. ifconfig命令 - 网络接口配置器 ifconfig&#xff08;interface configurator&#xff09;是Linux系统中最基础的网络配置工具。该命令可以初始化网络接口、分配IP地址、启用或禁用接口&#xff0c;同时还能查看接口的详细信息。 查看网络接口信息 # ifconfig eth0 …

Python Pytest-Benchmark详解:精准性能测试的利器

在软件开发的迭代过程中&#xff0c;性能优化如同精密手术&#xff0c;需要精准的测量工具。Pytest-Benchmark作为pytest生态中的性能测试插件&#xff0c;凭借其无缝集成能力和专业统计功能&#xff0c;成为Python开发者进行基准测试的首选工具。本文将深入解析其技术特性与实…

60天python训练营打卡day51

学习目标&#xff1a; 60天python训练营打卡 学习内容&#xff1a; DAY 51 复习日 作业&#xff1a;day43的时候我们安排大家对自己找的数据集用简单cnn训练&#xff0c;现在可以尝试下借助这几天的知识来实现精度的进一步提高 学习时间&#xff1a; 2025.07.04 浙大疏锦行…

支持向量机(SVM)在肺部CT图像分类(肺癌检测)中的实现与优化

🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C++, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C++、C#等开发语言,熟悉Java常用开发技术,能熟练应用常用数据库SQL server,Oracle,mysql,postgresql等进行开发应用…

YOLOv3-SPP 深度解析:引入 SPP 结构,显著提升目标检测性能!

✅ YOLOv3-SPP 技术详解 一、前言 YOLOv3-SPP 是在 YOLOv3 基础上加入 SPP&#xff08;Spatial Pyramid Pooling&#xff09;模块的一种改进版本&#xff0c;旨在提升模型对不同尺度目标的识别能力&#xff0c;尤其是在大目标检测方面表现更优。 它由 Alexey Bochkovskiy 在…

负载均衡--常见负载均衡算法

负载均衡算法可以分为两类&#xff1a;静态负载均衡算法和动态负载均衡算法。 1、静态负载均衡算法包括&#xff1a;轮询&#xff0c;比率&#xff0c;优先权 轮询&#xff08;Round Robin&#xff09;&#xff1a;顺序循环将请求一次顺序循环地连接每个服务器。当其中某个服务…

深入解析GCC:开源的编译器之王

在编程世界中&#xff0c;编译器是将人类可读代码转化为机器指令的关键桥梁。而GCC&#xff08;GNU Compiler Collection&#xff09; 无疑是这个领域最耀眼的明星之一。作为开源世界的基石&#xff0c;它支撑着Linux内核、众多开源项目和商业软件的构建。今天&#xff0c;我们…

https和http有什么区别

目录 一、核心区别&#xff1a;是否基于加密传输 二、底层传输机制差异 三、HTTPS 的加密原理 四、应用场景差异 五、其他细节区别 总结 在网络通信中&#xff0c;HTTP&#xff08;Hypertext Transfer Protocol&#xff0c;超文本传输协议&#xff09; 和HTTPS&#xff0…

CSS3 文本效果详解

CSS3 文本效果详解 引言 随着Web技术的发展,CSS3为前端设计师和开发者提供了丰富的文本效果选项。这些效果不仅能够增强网页的美观性,还能提升用户体验。本文将详细介绍CSS3中的文本效果,包括文本阴影、文本描边、文本装饰、文本换行、文本大小写等,并探讨如何在实际项目…

MySQL 中 -> 和 ->> 操作符的区别

简介 MySQL 5.7 或更高版本&#xff0c;可以使用 ->> 和 -> 运算符简化语法这两个操作符都是用于提取 JSON 数据的&#xff0c;但有一些重要区别 -> 操作符 功能&#xff1a;提取 JSON 对象的指定路径的值 返回类型&#xff1a;返回 JSON 类型的值&#xff08;可…

Vue2 day07

1.vuex的基本认知2.构建多组件共享的数据环境步骤&#xff1a;1.在自己创建的文件夹下创建脚手架2.创建三个组件### 源代码如下App.vue在入口组件中引入 Son1 和 Son2 这两个子组件html <template><div id"app"><h1>根组件</h1><input ty…

简述MCP的原理-AI时代的USB接口

1 简介随着AI的不断发展&#xff0c;RAG&#xff08;检索增强生成&#xff09;和function calling等技术的出现&#xff0c;使得大语言模型的对话生成能力得到了增强。然而&#xff0c;function calling的实现逻辑比较复杂&#xff0c;一个简单的工具调用和实现方式需要针对不同…

CISSP知识点汇总-资产安全

CISSP知识点汇总 域1---安全与风险管理域2---资产安全域3---安全工程域4---通信与网络安全域5---访问控制域6---安全评估与测试域7---安全运营域8---应用安全开发域2 资产安全 一、资产识别和分类 1、信息分级(Classification): 按照敏感程度(机密性被破坏) 按照重要程度…