[多线程]基于阻塞队列(Blocking Queue)的生产消费者模型的实现
标题:[多线程]基于阻塞队列(Blocking Queue)的生产消费者模型的实现
@水墨不写bug

文章目录
- 一、生产者消费者模型特点:
- 二、实现
- 2.1详细解释
- 1. 成员变量
- 2. 构造函数
- 3. `Isfull` 和 `Isempty`
- 4. `Push` 函数
- 5. `Pop` 函数
- 6. 析构函数
- 7. `GetSize` 函数
- 三、总结与多线程分析
- 四、生产消费模型的优势与分析
一下的代码实现了一个阻塞队列(BlockQueue),用于生产者-消费者模型的实践。该模型允许生产者和消费者并发地进行数据生产和消费操作。
一、生产者消费者模型特点:
1.一个交易场所(特定数据结构形式存在的一段内存空间)
2.两种角色(生产者、消费者—生产线程、消费线程)
3.三种关系(生产者之间、消费者之间、生产者和消费者之间的关系)
与阻塞队列结合后形成的特点:
- 同步生产和消费操作:在多线程环境下,生产者和消费者可以同时操作队列,但需要保证线程安全。
- 避免资源浪费:当队列满时,生产者需要等待;当队列空时,消费者需要等待,以此避免资源浪费和无效操作。
- 提高效率:通过设置高水位线和低水位线,适时唤醒多个生产者或消费者,提高生产和消费的效率。
二、实现
#ifndef __BLOCK_QUEUE__
#define __BLOCK_QUEUE__#include <cstdlib>
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <queue>using std::cout;
using std::endl;// 阻塞队列的实现
// 生产消费模型的实践
// 生产和消费并发进行,3:三种关系(
// 当前实现是单生产,单消费,于是不需要考虑生产者与生产者之间、消费者与消费者之间的关系
// 仅仅考虑单个生产者和消费者之间的关系
// ——————当队列满,停止生产;队列为空,停止消费(线程同步)// ) 2:两个角色(consumer,creator) 1:一个交易场所(queue)template <class T>
class BlockQueue
{
private:// 判断队列是否已满bool Isfull(){return _queue.size() == _maxcap;}// 判断队列是否为空bool Isempty(){return _queue.empty();}public:// 构造函数,初始化队列的最大容量和相关参数BlockQueue(int maxcap = 10): _maxcap(maxcap), _queue(), _l_water(0), _h_water(maxcap), _call_num(maxcap / 2){if (maxcap <= 4) // 数据总体个数太少,不在处理这些细节{_l_water = 0x3f3f3f3f; // 无穷大_h_water = -0x3f3f3f3f; // 无穷小_call_num = 1;}else{_l_water = maxcap / 4;_h_water = maxcap * 3 / 4;}pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_p_cond, nullptr);pthread_cond_init(&_c_cond, nullptr);}// 生产者向队列中生产数据void Push(T &in){pthread_mutex_lock(&_mutex);while (Isfull())// 队列满了,停止生产,在pcond条件变量下等待{pthread_cond_wait(&_p_cond, &_mutex);}// 走到这里,要么等待之后被唤醒,要么队列没有满,可以加入数据_queue.push(in);pthread_cond_signal(&_c_cond);// 数据较多,叫醒消费者来消费if (_queue.size() >= _h_water){cout << "call lots comsumer" << endl;int t = _call_num;while (t--)pthread_cond_signal(&_c_cond);}pthread_mutex_unlock(&_mutex);}// 消费者拿队列内的数据void Pop(T *out){pthread_mutex_lock(&_mutex);while (Isempty())// 队列空了,停止消费,在ccond条件变量下等待{pthread_cond_wait(&_c_cond, &_mutex);}// 走到这里,要么等待之后被唤醒,要么队列没有空,可以拿出数据*out = _queue.front();_queue.pop();pthread_cond_signal(&_p_cond);// 数据较少,叫醒生产者来生产if (_queue.size() <= _l_water){cout << "call lots creator" << endl;int t = _call_num;while (t--)pthread_cond_signal(&_p_cond);}pthread_mutex_unlock(&_mutex);}// 析构函数,销毁互斥锁和条件变量~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_p_cond);pthread_cond_destroy(&_c_cond);}// 获取队列当前大小size_t GetSize(){return _queue.size();}private:std::queue<int> _queue; // 交易场所int _maxcap; // 队列最大数据个数pthread_mutex_t _mutex; // 维护生产和消费的互斥关系// 生产者和消费者在不同的条件变量下等待pthread_cond_t _p_cond;pthread_cond_t _c_cond;int _l_water; // 低水平线,低于这条线,叫醒更多的生产者生产int _h_water; // 高水平线,高于这条线,叫醒更多的消费者消费int _call_num; // 一次叫醒大量的合适值
};#endif
2.1详细解释
1. 成员变量
std::queue<int> _queue:标准库中的队列,用于存储数据。int _maxcap:队列的最大容量。pthread_mutex_t _mutex:互斥锁,用于保证生产和消费操作的互斥,避免数据竞争。pthread_cond_t _p_cond和pthread_cond_t _c_cond:条件变量,分别用于生产者和消费者的等待和唤醒。int _l_water和int _h_water:低水位线和高水位线,用于优化生产和消费的效率。当队列中的数据量低于低水位线时,唤醒生产者;当数据量高于高水位线时,唤醒消费者。int _call_num:一次性唤醒的生产者或消费者数量。
2. 构造函数
BlockQueue(int maxcap = 10)
- 初始化队列的最大容量和相关参数。
- 根据最大容量设置高水位线和低水位线,确保在数据量较多或较少时唤醒适当数量的生产者或消费者。
- 初始化互斥锁和条件变量。
3. Isfull 和 Isempty
bool Isfull():判断队列是否已满。通过比较队列当前大小和最大容量来实现。bool Isempty():判断队列是否为空。通过检查队列是否为空来实现。
4. Push 函数
void Push(T &in)
- 生产者向队列中添加数据。
- 先获取互斥锁,进入临界区。
- 如果队列已满,生产者在
_p_cond条件变量上等待。 - 向队列中添加数据后,唤醒在
_c_cond条件变量上等待的消费者。 - 如果队列中的数据量达到或超过高水位线,唤醒多个消费者。
- 释放互斥锁,退出临界区。
5. Pop 函数
void Pop(T *out)
- 消费者从队列中取数据。
- 先获取互斥锁,进入临界区。
- 如果队列为空,消费者在
_c_cond条件变量上等待。 - 从队列中取出数据后,唤醒在
_p_cond条件变量上等待的生产者。 - 如果队列中的数据量低于或等于低水位线,唤醒多个生产者。
- 释放互斥锁,退出临界区。
6. 析构函数
~BlockQueue()
- 销毁互斥锁和条件变量,释放资源。
7. GetSize 函数
size_t GetSize()
- 返回队列的当前大小。
三、总结与多线程分析
总结:
这段代码通过互斥锁和条件变量实现了一个线程安全的阻塞队列,能够有效地处理生产者和消费者之间的同步问题。通过设置高水位线和低水位线,可以在数据量较多或较少时适时唤醒多个生产者或消费者,提高队列的使用效率。
多线程分析:
上面的代码实现是对应一个生产者一个消费者的情况。多生产多消费起始只需要创建多个生产者线程,多个消费者线程即可。
因为临界区一次只允许一个线程访问,而这个线程是从哪来的?
对于生产者而言,生产者内部首先需要竞争出来一个生产者;
消费者也一样,需要竞争出来一个消费者;
然后,优胜的生产者和优胜的消费者之间还要进行一次竞争。
这一过程具体形象的解释了为什么这段代码可以不用改变就可以实现多生产多消费的情况。
四、生产消费模型的优势与分析
优势:
1.协调忙闲不均;
2.效率高;
3.实现生产者和消费者之间的解耦和;
整个系统共用一把锁,意味着一次只能有一个线程访问临界区。多生产多消费相对于单生产单消费而言,高效体现在哪里?
对于临界区的访问,多生产多消费和单生产单消费是没有区别的串行访问。
但是产生任务,解决任务也需要耗费时间。高效体现在生产者之间和消费者之间的并发:一个生产者访问队列的时候,其他生产者也在生产数据(构建请求)。消费者访问队列的时候,其他消费者也在消耗数据(解决请求)。
完~
转载请注明出处

相关文章:
[多线程]基于阻塞队列(Blocking Queue)的生产消费者模型的实现
标题:[多线程]基于阻塞队列(Blocking Queue)的生产消费者模型的实现 水墨不写bug 文章目录 一、生产者消费者模型特点:二、实现2.1详细解释1. 成员变量2. 构造函数3. Isfull 和 Isempty4. Push 函数5. Pop 函数6. 析构函数7. GetSize 函数 三、总结与多线…...
vue组件库el-menu导航菜单设置index,地址不会变更的问题
请先确认 1.路由已配置好 route-index.js如下, 2.view-ProHome.vue中已预留路由展示位 3.导航菜单复制组件库,并做修改 其中index与路由配置的地址一致 运行后发现点击菜单,url地址还是不变,查看组件库 Element - The worlds …...
JavaScript通过文件地址获取文件名称
在 JavaScript 中,可以通过文件链接地址提取文件名称。文件名称通常是链接中最后一个 / 之后的部分,可能还包含查询参数或哈希片段。以下是几种常见的提取文件名称的方法: 方法 1:使用 URL 对象和 pathname URL 对象可以解析链接…...
MySQL 优化方案
一、MySQL 查询过程 MySQL 查询过程是指从客户端发送 SQL 语句到 MySQL 服务器,再到服务器返回结果集的整个过程。这个过程涉及多个组件的协作,包括连接管理、查询解析、优化、执行和结果返回等。 1.1 查询过程的关键组件 连接管理器:管理…...
智能对话小程序功能优化day1-登录鉴权
目录 1.数据库表构建。 2.完善登录相关的实例对象。 3.登录相关功能实现。 4.小程序效果。 最近尝试下trae加入claude3.7后的读图生成代码功能,可以看到简单的页面一次性生成确实准确率高了不少,想起来之前笔记中开发的智能问答小程序功能还是有些简…...
【架构艺术】Go语言微服务monorepo的代码架构设计
近期因为项目架构升级原因,笔者着手调研一些go项目monorepo的代码架构设计,目标是长期把既有微服务项目重要的部分都转移到monorepo上面,让代码更容易维护,协作开发更加方便。虽然经验不多,但既然有了初步的调研&#…...
MinIO的预签名直传机制
我们传统使用MinIo做OSS对象存储的应用方式往往都是在后端配置与MinIO的连接和文件上传下载的相关接口,然后我们在前端调用这些接口完成文件的上传下载机制,但是,当并发量过大,频繁访问会对后端的并发往往会对服务器造成极大的压力…...
谈谈List,Set,Map的区别
List、Set 和 Map 是 Java 集合框架(Java Collections Framework)中的三种主要接口,它们各自有不同的特点和用途。以下是它们的区别和使用场景的详细解释: 1. List(列表) 1.1 特点 有序集合:Li…...
投资晚报 3.12
一、 晚间要闻 1、CME美联储观察:美联储3月降息25个基点的概率为3% 3 月 12 日,据 CME「美联储观察」数据,美联储 3 月降息 25 个基点的概率为 3%,维持不变的概率为 97%。 2、美国劳工统计局将于今晚20:30公布2月CPI数据 3 月…...
蓝桥 2109统计子矩阵
问题描述 给定一个NM 的矩阵 A, 请你统计有多少个子矩阵 (最小 11, 最大 NM) 满足子矩阵中所有数的和不超过给定的整数 K ? 输入格式 第一行包含三个整数 N,M 和 K. 之后 NN 行每行包含 M 个整数, 代表矩阵 A. 输出格式 一个整数代表答案。 样例输入 3 4 10 1 2 3 4 5…...
Qt开源控件库(qt-material-widgets)的编译及使用
项目简介 qt-material-widgets是一个基于 Qt 小部件的 Material Design 规范实现。 项目地址 项目地址:qt-material-widgets 本地构建环境 Win11 家庭中文版 VS2019 Qt5.15.2 (MSVC2019) 本地构建流程 克隆后的目录结构如图: 直接使用Qt Crea…...
vue的 props 与 $emit 以及 provide 与 inject 的 组件之间的传值对比
好的,下面是 props 与 $emit 以及 provide 与 inject 的对比: 1. props 与 $emit props:父组件通过 props 向子组件传递数据,子组件接收后不可修改。子组件只能读取 props 传递给它的数据。如果需要修改或更新父组件的状态&#…...
用python批量生成文件夹
问题描述 当批量生成文件夹时,手动右键创建文件夹是一个繁琐的过程,尤其是文件夹的命名过程。假设从3月10日到3月19日,每天要为某个日常工作创建一个名为2025031x的文件夹,手动创建文件夹并命名费时费力。 百度给出了以下四种方法…...
Json 转义符号处理(Mongo changeStream op log)
使用mongo-kafka组件订阅mongo的changeStream得到 一个带有很多转义符号的json字符串 "{\"_id\": {\"_data\": \"8267D0F733000001502B022C0100296E5A1004366730C56F7E41A790BDA4CF23259A4F46645F6964006467B91713A024A00E32CDF6800004\"},…...
懒加载(Lazy Loading):原理、实现与优化策略
懒加载(Lazy Loading) 是一种优化网页性能的技术,主要用于延迟加载非关键资源(如图片、视频、脚本等),直到它们真正需要被使用时才加载。懒加载可以显著减少页面初始加载时间,降低带宽消耗&…...
dns劫持是什么?常见的劫持类型有哪些?如何预防?
DNS劫持的定义 DNS劫持(Domain Name System Hijacking)是一种网络攻击手段,攻击者通过篡改域名解析的过程,将用户对某个域名的访问请求重定向到错误或恶意的IP地址。这种攻击可能导致用户访问到钓鱼网站、恶意广告页面࿰…...
蓝桥杯省赛真题C++B组2024-握手问题
一、题目 【问题描述】 小蓝组织了一场算法交流会议,总共有 50 人参加了本次会议。在会议上,大家进行了握手交流。按照惯例他们每个人都要与除自己以外的其他所有人进行一次握手(且仅有一次)。但有 7 个人,这 7 人彼此之间没有进行握手(但这…...
【MySQL】基本操作 —— DDL
目录 DDLDDL 常用操作对数据库的常用操作查看所有数据库创建数据库切换、显示当前数据库删除数据库修改数据库编码 对表的常用操作创建表数据类型数值类型日期和时间类型字符串类型 查看当前数据库所有表查看指定表的创建语句查看指定表结构删除表 对表结构的常用操作给表添加字…...
XML语法
一、XML简介 (一)定义 XML(eXtensible Markup Language,可扩展标记语言)是一种简单的文本格式,用于标记电子文件使其具有结构性的标记语言。它与HTML(HyperText Markup Language,超…...
游戏引擎学习第152天
仓库:https://gitee.com/mrxiao_com/2d_game_3 回顾昨天的内容 这个节目展示了我们如何从零开始制作一款完整的游戏。我们不使用任何游戏引擎或库,而是从头开始创建一款游戏,整个开发过程都会呈现给大家。你将能够看到每一行代码的编写,了解…...
考研数学非数竞赛复习之Stolz定理求解数列极限
在非数类大学生数学竞赛中,Stolz定理作为一种强大的工具,经常被用来解决和式数列极限的问题,也被誉为离散版的’洛必达’方法,它提供了一种简洁而有效的方法,使得原本复杂繁琐的极限计算过程变得直观明了。本文&#x…...
故障诊断——neo4j入门
文章目录 neo4jQuickStartDemo neo4j QuickStart 详情可见博客:https://www.cnblogs.com/nhdlb/p/18703804,使用docker拉取最近的一个版本进行创建 docker run -it -d -p 7474:7474 -p 7687:7687 \ -v /disk5/neo4j_docker/data:/data \ -v /disk5/ne…...
【CXX】6.2 str — rust::Str
Rust::Str 公共 API // rust/cxx.hclass Str final { public:Str() noexcept;Str(const Str &) noexcept;Str(const String &) noexcept;// 如果输入不是 UTF-8,抛出 std::invalid_argument 异常。Str(const std::string &);Str(const char *);Str(con…...
【JavaWeb】快速入门——HTMLCSS
文章目录 一、 HTML简介1、HTML概念2、HTML文件结构3、可视化网页结构 二、 HTML标签语法1、标题标签2、段落标签3、超链接4、换行5、无序列表6、路径7、图片8、块1 盒子模型2 布局标签 三、 使用HTML表格展示数据1、定义表格2、合并单元格横向合并纵向合并 四、 使用HTML表单收…...
unordered_set 的常用函数
在 C 的标准库中,std::unordered_set 是基于哈希表实现的哈希集合。下面介绍这种语言里哈希集合的常用函数。 C std::unordered_set 1. 元素操作 insert 功能:向哈希集合中插入元素。如果元素已经存在,则不会重复插入。示例代码:…...
若依框架-给sys_user表添加新字段并获取当前登录用户的该字段值
目录 添加字段 修改SysUser类 修改SysUserMapper.xml 修改user.js 前端获取字段值 添加字段 若依框架的sys_user表是没有age字段的,但由于业务需求,我需要新添加一个age字段: 修改SysUser类 添加age字段后,要在SysUser类 …...
前端监测窗口尺寸和元素尺寸变化的方法
前端监测窗口尺寸变化和元素尺寸变化的方法 window.resize 简介 window.resize事件是浏览器提供的一种事件,用于监听窗口大小的改变。这意味着当用户调整浏览器窗口大小时,相关的JavaScript代码将被触发执行。这为开发者提供了一种机制,可…...
angular中下载接口返回文件
目录 一、URL.createObjectURL() 一、URL.createObjectURL() createObjectURL属于js的原生方法,位于window.URL上,用于将Blob或者File文件转换为可以临时的URL地址进行显示 **注意**:Angular 的 HttpClient 默认将响应解析为 JSON 对象16。…...
ubuntu 部署deepseek
更新 apt update 升级 apt upgrade 格式化硬盘 mkfs.ext4 /dev/sdb 安装nginx 查看端口 一、安装Ollama Ollama是一个开源的大型语言模型(LLM)推理服务器,为用户提供了灵活、安全和高性能的语言模型推理解决方案。 ollama/docs/linux.m…...
【每日学点HarmonyOS Next知识】拖拽调整列表顺序、tab回弹、自定义弹窗this、状态变量修饰枚举
1、HarmonyOS 功能实现(拖拽调整列表顺序)? 可参考: import curves from ohos.curves; import Curves from ohos.curvesEntry Component struct ListItemExample {State private arr: number[] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]…...
