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

news2025/3/13 21:31:34

标题:[多线程]基于阻塞队列(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.coloradmin.cn/o/2314499.html

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

相关文章

vue组件库el-menu导航菜单设置index,地址不会变更的问题

请先确认 1.路由已配置好 route-index.js如下&#xff0c; 2.view-ProHome.vue中已预留路由展示位 3.导航菜单复制组件库&#xff0c;并做修改 其中index与路由配置的地址一致 运行后发现点击菜单&#xff0c;url地址还是不变&#xff0c;查看组件库 Element - The worlds …

MySQL 优化方案

一、MySQL 查询过程 MySQL 查询过程是指从客户端发送 SQL 语句到 MySQL 服务器&#xff0c;再到服务器返回结果集的整个过程。这个过程涉及多个组件的协作&#xff0c;包括连接管理、查询解析、优化、执行和结果返回等。 1.1 查询过程的关键组件 连接管理器&#xff1a;管理…

智能对话小程序功能优化day1-登录鉴权

目录 1.数据库表构建。 2.完善登录相关的实例对象。 3.登录相关功能实现。 4.小程序效果。 最近尝试下trae加入claude3.7后的读图生成代码功能&#xff0c;可以看到简单的页面一次性生成确实准确率高了不少&#xff0c;想起来之前笔记中开发的智能问答小程序功能还是有些简…

MinIO的预签名直传机制

我们传统使用MinIo做OSS对象存储的应用方式往往都是在后端配置与MinIO的连接和文件上传下载的相关接口&#xff0c;然后我们在前端调用这些接口完成文件的上传下载机制&#xff0c;但是&#xff0c;当并发量过大&#xff0c;频繁访问会对后端的并发往往会对服务器造成极大的压力…

Qt开源控件库(qt-material-widgets)的编译及使用

项目简介 qt-material-widgets是一个基于 Qt 小部件的 Material Design 规范实现。 项目地址 项目地址&#xff1a;qt-material-widgets 本地构建环境 Win11 家庭中文版 VS2019 Qt5.15.2 (MSVC2019) 本地构建流程 克隆后的目录结构如图&#xff1a; 直接使用Qt Crea…

用python批量生成文件夹

问题描述 当批量生成文件夹时&#xff0c;手动右键创建文件夹是一个繁琐的过程&#xff0c;尤其是文件夹的命名过程。假设从3月10日到3月19日&#xff0c;每天要为某个日常工作创建一个名为2025031x的文件夹&#xff0c;手动创建文件夹并命名费时费力。 百度给出了以下四种方法…

【MySQL】基本操作 —— DDL

目录 DDLDDL 常用操作对数据库的常用操作查看所有数据库创建数据库切换、显示当前数据库删除数据库修改数据库编码 对表的常用操作创建表数据类型数值类型日期和时间类型字符串类型 查看当前数据库所有表查看指定表的创建语句查看指定表结构删除表 对表结构的常用操作给表添加字…

游戏引擎学习第152天

仓库:https://gitee.com/mrxiao_com/2d_game_3 回顾昨天的内容 这个节目展示了我们如何从零开始制作一款完整的游戏。我们不使用任何游戏引擎或库&#xff0c;而是从头开始创建一款游戏&#xff0c;整个开发过程都会呈现给大家。你将能够看到每一行代码的编写&#xff0c;了解…

考研数学非数竞赛复习之Stolz定理求解数列极限

在非数类大学生数学竞赛中&#xff0c;Stolz定理作为一种强大的工具&#xff0c;经常被用来解决和式数列极限的问题&#xff0c;也被誉为离散版的’洛必达’方法&#xff0c;它提供了一种简洁而有效的方法&#xff0c;使得原本复杂繁琐的极限计算过程变得直观明了。本文&#x…

故障诊断——neo4j入门

文章目录 neo4jQuickStartDemo neo4j QuickStart 详情可见博客&#xff1a;https://www.cnblogs.com/nhdlb/p/18703804&#xff0c;使用docker拉取最近的一个版本进行创建 docker run -it -d -p 7474:7474 -p 7687:7687 \ -v /disk5/neo4j_docker/data:/data \ -v /disk5/ne…

【JavaWeb】快速入门——HTMLCSS

文章目录 一、 HTML简介1、HTML概念2、HTML文件结构3、可视化网页结构 二、 HTML标签语法1、标题标签2、段落标签3、超链接4、换行5、无序列表6、路径7、图片8、块1 盒子模型2 布局标签 三、 使用HTML表格展示数据1、定义表格2、合并单元格横向合并纵向合并 四、 使用HTML表单收…

若依框架-给sys_user表添加新字段并获取当前登录用户的该字段值

目录 添加字段 修改SysUser类 修改SysUserMapper.xml 修改user.js 前端获取字段值 添加字段 若依框架的sys_user表是没有age字段的&#xff0c;但由于业务需求&#xff0c;我需要新添加一个age字段&#xff1a; 修改SysUser类 添加age字段后&#xff0c;要在SysUser类 …

前端监测窗口尺寸和元素尺寸变化的方法

前端监测窗口尺寸变化和元素尺寸变化的方法 window.resize 简介 window.resize事件是浏览器提供的一种事件&#xff0c;用于监听窗口大小的改变。这意味着当用户调整浏览器窗口大小时&#xff0c;相关的JavaScript代码将被触发执行。这为开发者提供了一种机制&#xff0c;可…

ubuntu 部署deepseek

更新 apt update 升级 apt upgrade 格式化硬盘 mkfs.ext4 /dev/sdb 安装nginx 查看端口 一、安装Ollama Ollama是一个开源的大型语言模型&#xff08;LLM&#xff09;推理服务器&#xff0c;为用户提供了灵活、安全和高性能的语言模型推理解决方案。 ollama/docs/linux.m…

MySQL库和表的操作详解:从创建库到表的管理全面指南

目录 一、MySQL库的操作详解 〇、登录MySQL 一、数据库的创建与字符集设置 1. 创建数据库的语法 2. 创建数据库示例 查看创建出来的文件: bash下查看MySQL创建的文件 二、字符集与校验规则 1. 查看系统默认设置 2. 查看支持的字符集与校验规则 3. 校验规则对查询的影响…

PyTorch 系列教程:使用CNN实现图像分类

图像分类是计算机视觉领域的一项基本任务&#xff0c;也是深度学习技术的一个常见应用。近年来&#xff0c;卷积神经网络&#xff08;cnn&#xff09;和PyTorch库的结合由于其易用性和鲁棒性已经成为执行图像分类的流行选择。 理解卷积神经网络&#xff08;cnn&#xff09; 卷…

Java 大视界 -- Java 大数据中的数据可视化大屏设计与开发实战(127)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

「Unity3D」UGUI将元素固定在,距离屏幕边缘的某个比例,以及保持元素自身比例

在不同分辨率的屏幕下&#xff0c;UI元素按照自身像素大小&#xff0c;会发生位置与比例的变化&#xff0c;本文仅利用锚点&#xff08;Anchors&#xff09;使用&#xff0c;来实现UI元素&#xff0c;固定在某个比例距离的屏幕边缘。 首先&#xff0c;将元素的锚点设置为中心&…

Deep research深度研究:ChatGPT/ Gemini/ Perplexity/ Grok哪家最强?(实测对比分析)

目前推出深度研究和深度检索的AI大模型有四家&#xff1a; OpenAI和Gemini 的deep research&#xff0c;以及Perplexity 和Grok的deep search&#xff0c;都能生成带参考文献引用的主题报告。 致力于“几分钟之内生成一份完整的主题调研报告&#xff0c;解决人力几小时甚至几天…

关于sqlalchemy的ORM的使用

关于sqlalchemy的ORM的使用 二、创建表三、使用数据表、查询记录三、批量插入数据四、关于with...as...:的使用 二、创建表 使用Mapped来映射字段 from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker,Mapped,mapped_columnBa…