[多线程]基于阻塞队列(Blocking Queue)的生产消费者模型的实现

embedded/2025/3/14 11:51:21/

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

在这里插入图片描述


文章目录

  • 一、生产者消费者模型特点:
  • 二、实现
  • 2.1详细解释
    • 1. 成员变量
    • 2. 构造函数
    • 3. `Isfull` 和 `Isempty`
    • 4. `Push` 函数
    • 5. `Pop` 函数
    • 6. 析构函数
    • 7. `GetSize` 函数
  • 三、总结与多线程分析
  • 四、生产消费模型的优势与分析


一下的代码实现了一个阻塞队列(BlockQueue),用于生产者-消费者模型的实践。该模型允许生产者消费者并发地进行数据生产和消费操作。

一、生产者消费者模型特点:

1.一个交易场所(特定数据结构形式存在的一段内存空间)
2.两种角色(生产者消费者—生产线程、消费线程)
3.三种关系(生产者之间、消费者之间、生产者消费者之间的关系)

阻塞队列结合后形成的特点:

  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_condpthread_cond_t _c_cond:条件变量,分别用于生产者消费者的等待和唤醒。
  • int _l_waterint _h_water:低水位线和高水位线,用于优化生产和消费的效率。当队列中的数据量低于低水位线时,唤醒生产者;当数据量高于高水位线时,唤醒消费者
  • int _call_num:一次性唤醒的生产者消费者数量。

2. 构造函数

BlockQueue(int maxcap = 10)
  • 初始化队列的最大容量和相关参数。
  • 根据最大容量设置高水位线和低水位线,确保在数据量较多或较少时唤醒适当数量的生产者消费者
  • 初始化互斥锁和条件变量。

3. IsfullIsempty

  • 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.实现生产者消费者之间的解耦和;

整个系统共用一把锁,意味着一次只能有一个线程访问临界区。多生产多消费相对于单生产单消费而言,高效体现在哪里?
对于临界区的访问,多生产多消费和单生产单消费是没有区别的串行访问
但是产生任务,解决任务也需要耗费时间。高效体现在生产者之间和消费者之间的并发一个生产者访问队列的时候,其他生产者也在生产数据(构建请求)。消费者访问队列的时候,其他消费者也在消耗数据(解决请求)。


完~
转载请注明出处

在这里插入图片描述


http://www.ppmy.cn/embedded/172481.html

相关文章

专业视角:set 和 multiset的原理与应用解析

文章目录 前言关联式容器键值对树形结构的关联式容器 set1. set 的定义注意以下八点&#xff1a;2. set 的常用操作3. set 的迭代器4. set 的底层实现5. 自定义排序&#xff08;比较器&#xff09;6. set 与 multiset 的区别7. 示例&#xff1a;set 的完整代码 multiset 前言 …

广播机制(Broadcasting)

NumPy 默认是元素与元素的操作 所以当我们使用常规的运算符&#xff0c;如 、-、*、/。默认是元素与元素相操作&#xff08;pointwise operation&#xff09;。 import numpy as np a np.array([1.0, 2.0, 3.0]) b np.array([2.0, 2.0, 2.0]) print(a b) print(a - b) pri…

【实战ES】实战 Elasticsearch:快速上手与深度实践-8.2.1AWS OpenSearch无服务器方案

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 8.2.1AWS OpenSearch 无服务器方案深度解析与实践指南1. Serverless架构的核心价值与行业趋势1.1 传统Elasticsearch集群的运维挑战1.2 Serverless技术演进路线技术特性对比…

基于ASP.NET校园二手交易网站设计与实现

摘要 校园二手交易市场伴随互联网技术的迅猛发展以及校园内资源共享需求的增长而逐渐兴起。基于ASP.NET开发的校园二手交易网站&#xff0c;目的在于为高校师生打造一个便捷、安全且高效的二手物品交易平台。此网站借助ASP.NET技术开展开发工作&#xff0c;并且结合SQL Server数…

盛夏将至,风扇灯技术的革新和趋势解读--其利天下技术BLDC算法提供商

风扇灯是2003年进入中国市场的&#xff0c;凭借着节省空间、可调节性强和样式多样化等优势俘获不少消费者的芳心。 数据显示&#xff0c;2022年风扇灯的市场规模为26.54亿美元&#xff0c;预计到2030年将达到63.33亿美元&#xff0c;2023年至2030年的复合年增长率为9.61%。在市…

手写一个Tomcat

Tomcat 是一个广泛使用的开源 Java Servlet 容器&#xff0c;用于运行 Java Web 应用程序。虽然 Tomcat 本身功能强大且复杂&#xff0c;但通过手写一个简易版的 Tomcat&#xff0c;我们可以更好地理解其核心工作原理。本文将带你一步步实现一个简易版的 Tomcat&#xff0c;并深…

日常开发记录-radioGroup组件

日常开发记录-radioGroup组件 1.前提2.问题&#xff1a;无限循环调用3.解释Vue 事件传播机制分析与无限循环原因解释4.解决 1.前提 在上一章的&#xff0c;我们实现了radio组件。从这进入了解 新增个radioGroup组件呢。 <template><divclass"q-radio-group&quo…

并行算法_第十章_《C++并发编程实战》笔记

并行算法 1. 核心知识点2. 使用并行算法3. 数据竞争与线程安全4. 性能优化5. 常见问题与陷阱6. 最佳实践7. 多选题7.1 题目7.2 多选答案 9. 设计题目10. 设计题目答案 1. 核心知识点 执行策略&#xff08;Execution Policies&#xff09; C17引入的执行策略允许开发者指定算法…