【Linux | C++ 】基于环形队列的多生产者多消费者模型(Linux系统下C++ 代码模拟实现)

news2025/1/19 14:33:14

在这里插入图片描述

阅读导航

  • 引言
  • 一、生产者消费者模型
  • 二、环形队列简介
  • 三、基于环形队列的生产者消费者模型(C++ 代码模拟实现)
    • ⭕Makefile文件
    • ⭕ . h 头文件
      • ✅sem.hpp
      • ✅ringQueue.hpp
    • ⭕ . cpp 文件
      • ✅testMain.cpp
  • 温馨提示

引言

在上一篇文章中,我们深入探讨了Linux操作系统中的POSIX信号量,这是一个强大的同步机制,用于协调进程或线程对共享资源的访问。通过对信号量的深入理解和应用,我们学习了如何有效地解决并发编程中的竞争条件,确保程序的稳定性和效率。随着并发编程技术的不断深入,理解和掌握更多同步模型对于开发高性能、可靠的软件系统变得尤为重要。因此,本篇文章将继续我们的并发编程之旅,引入一个经典且实用的同步模型——基于环形队列的生产者消费者模型

在本文中,我们将详细探讨基于环形队列的生产者消费者模型的设计和实现。我们将介绍环形队列的数据结构,分析生产者和消费者之间的同步机制,探索如何利用前文提到的POSIX信号量以及其他同步工具(如互斥锁)来实现生产者和消费者之间高效、安全的数据交换。通过具体的代码示例和案例分析,读者将能够深入理解生产者消费者模型的工作原理,掌握如何在实际项目中设计和实现基于环形队列的高效同步模型。

探索基于环形队列的生产者消费者模型,不仅能够加深我们对并发编程同步机制的理解,还能够提升我们解决实际问题的能力。让我们一起继续并发编程的探索之旅,解锁更多的编程技巧和知识。

一、生产者消费者模型

生产者消费者模型是并发编程中一个经典且重要的问题模型,它描述了两类主体——生产者(Producer)和消费者(Consumer)在并发环境下对共享资源(通常是缓冲区或队列)的访问模式。生产者负责生成数据并将其放入缓冲区,而消费者则从缓冲区取出数据进行处理。该模型的核心在于解决生产者和消费者之间的同步与通信问题,保证数据在生产和消费时的一致性和可用性,同时避免资源的冲突和浪费。对于希望深入了解生产者消费者模型的读者,我们在之前的内容中有所介绍——链接:⭕生产者消费者模型

通过上述简介,希望读者能够对生产者消费者模型有一个初步的认识和理解。在并发编程的实践中,该模型不仅是一个常见的问题场景,也提供了一种思考并发问题的方法论,对于提高编程技能和系统设计能力都有重要意义。

二、环形队列简介

环形队列是一种固定大小的、使用数组实现的队列数据结构,特别在于其首尾相连的循环特性。这种结构允许当数组达到其容量上限时,新加入的元素可以放置在数组的开始位置(如果那里有空位)。环形队列的这一设计使得它在空间利用和操作效率上具有显著优势,尤其适用于有固定缓冲区需求的场景。

🚩主要特点包括:

  • 固定大小:一旦创建,队列的大小就固定不变。
  • 高效操作:入队和出队操作都非常高效,因为它们仅涉及指针的简单移动。
  • 两个指针:使用头指针和尾指针来分别追踪队列的第一个和最后一个元素。

环形队列广泛应用于操作系统、网络通信、生产者消费者模型等多个领域,特别是在需要高效管理固定缓冲区资源的场合。实现环形队列时,关键在于正确管理头尾指针的位置,并准确判断队列的空或满状态。
在这里插入图片描述

三、基于环形队列的生产者消费者模型(C++ 代码模拟实现)

⭕Makefile文件

ring_queue:testMain.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f ring_queue

这段代码是一个Makefile脚本,用于编译和清理一个名为ring_queue的项目。

⭕ . h 头文件

✅sem.hpp

// 防止头文件重复包含的预处理指令。
#ifndef _SEM_HPP_
#define _SEM_HPP_

// 引入输入输出流库,虽然在此代码中未直接使用,可能为后续扩展预留。
#include <iostream>
// 引入POSIX信号量的头文件。
#include <semaphore.h>

// 定义一个类 Sem。
class Sem
{
public:
    // 构造函数,接收一个整数value作为信号量的初始值。
    Sem(int value)
    {
        // 初始化信号量,其中&sem_是信号量对象的地址,
        // 0表示信号量是当前进程的局部信号量,
        // value是信号量的初始值。
        sem_init(&sem_, 0, value);
    }
    
    // p操作,也称为wait操作,用于减少信号量的值。
    // 如果信号量的值为0,则调用此方法的线程将阻塞,直到信号量的值大于0。
    void p()
    {
        sem_wait(&sem_);
    }
    
    // v操作,也称为signal操作,用于增加信号量的值。
    // 如果有其他线程因为等待此信号量而阻塞,则它们中的一个将被唤醒。
    void v()
    {
        sem_post(&sem_);
    }
    
    // 析构函数,用于销毁信号量。
    ~Sem()
    {
        sem_destroy(&sem_);
    }

private:
    // 私有成员变量,存储信号量对象的实例。
    sem_t sem_;
};

// 预处理指令的结束标志。
#endif

这个Sem类提供了简单的接口来进行信号量的基本操作:初始化(构造函数)、等待(p方法)、信号(v方法)和销毁(析构函数)。通过这个类,可以更方便地在C++项目中使用POSIX信号量进行同步操作

✅ringQueue.hpp

// 防止头文件重复包含的预处理指令。
#ifndef _Ring_QUEUE_HPP_
#define _Ring_QUEUE_HPP_

// 引入所需的头文件。
#include <iostream>
#include <vector>
#include <pthread.h>
#include "sem.hpp"

// 定义一个全局常量作为队列的默认大小。
const int g_default_num = 5;

// 定义一个模板类RingQueue,用于实现环形队列。
template<class T>
class RingQueue
{
public:
    // 构造函数,参数default_num指定队列的大小,默认为g_default_num。
    RingQueue(int default_num = g_default_num)
    : ring_queue_(default_num), 
      num_(default_num),
      c_step(0),
      p_step(0),
      space_sem_(default_num), // 初始化空间信号量,表示可用空间数量。
      data_sem_(0) // 初始化数据信号量,表示队列中的数据项数量。
    {
        pthread_mutex_init(&clock, nullptr); // 初始化消费者互斥锁。
        pthread_mutex_init(&plock, nullptr); // 初始化生产者互斥锁。
    }
    
    // 析构函数,销毁互斥锁。
    ~RingQueue()
    {
        pthread_mutex_destroy(&clock);
        pthread_mutex_destroy(&plock);
    }
    
    // push方法,生产者调用,向队列中添加元素。
    void push(const T &in)
    {
        space_sem_.p(); // 等待有空间可写。
        pthread_mutex_lock(&plock); // 获取生产者互斥锁。
        ring_queue_[p_step++] = in; // 将元素添加到队列中。
        p_step %= num_; // 环形逻辑,如果到达末尾则回到开始。
        pthread_mutex_unlock(&plock); // 释放生产者互斥锁。
        data_sem_.v(); // 增加数据信号量,表示有新数据可读。
    }
    
    // pop方法,消费者调用,从队列中取出元素。
    void pop(T *out)
    {
        data_sem_.p(); // 等待有数据可读。
        pthread_mutex_lock(&clock); // 获取消费者互斥锁。
        *out = ring_queue_[c_step++]; // 从队列中取出元素。
        c_step %= num_; // 环形逻辑,如果到达末尾则回到开始。
        pthread_mutex_unlock(&clock); // 释放消费者互斥锁。
        space_sem_.v(); // 增加空间信号量,表示有空间可写。
    }

private:
    std::vector<T> ring_queue_; // 使用vector存储队列元素。
    int num_; // 队列的大小。
    
    int c_step; // 消费者在队列中的当前位置。
    int p_step; // 生产者在队列中的当前位置。
    
    Sem space_sem_; // 控制队列空间的信号量。
    Sem data_sem_; // 控制队列中数据的信号量。
    pthread_mutex_t clock; // 消费者互斥锁。
    pthread_mutex_t plock; // 生产者互斥锁。
};

#endif   // 预处理指令的结束标志。

这个环形队列的实现利用信号量space_sem_data_sem_来控制队列的空间和数据,确保生产者不会在队列满时添加元素,消费者不会在队列空时尝试取出元素。同时,通过两个互斥锁clockplock分别保护消费者和生产者的操作,防止并发环境下的数据竞争问题。这样的设计使得RingQueue既能高效地管理数据,又能保证线程安全

⭕ . cpp 文件

✅testMain.cpp

// 包含RingQueue类的头文件。
#include "ringQueue.hpp"
#include <cstdlib> // 包含标准库,用于rand()等函数。
#include <ctime>   // 用于time()函数。
#include <sys/types.h> // 包含类型定义,例如pid_t。
#include <unistd.h>    // 包含各种常量和类型,并声明了各种函数,例如sleep()和getpid()。

// 消费者线程的工作函数。
void *consumer(void *args)
{
    RingQueue<int> *rq = (RingQueue<int> *)args; // 将传入的参数转换为RingQueue指针。
    while(true)
    {
        sleep(1); // 休眠1秒,模拟处理时间。
        int x;
        rq->pop(&x); // 从环形队列中取出一个元素。
        // 打印消费信息,包括消费的值和当前线程ID。
        std::cout << "消费: " << x << " [" << pthread_self() << "]" << std::endl;
    }
}

// 生产者线程的工作函数。
void *productor(void *args)
{
    RingQueue<int> *rq = (RingQueue<int> *)args; // 将传入的参数转换为RingQueue指针。
    while(true)
    {
        int x = rand() % 100 + 1; // 生成一个1到100之间的随机数。
        // 打印生产信息,包括生产的值和当前线程ID。
        std::cout << "生产: " << x << " [" << pthread_self() << "]" << std::endl;
        rq->push(x); // 将生成的随机数放入环形队列中。
    }
}

int main()
{
    srand((uint64_t)time(nullptr) ^ getpid()); // 设置随机数种子,确保每次运行结果不同。
    RingQueue<int> *rq = new RingQueue<int>(); // 创建一个RingQueue对象。
    pthread_t c[3], p[2]; // 定义线程ID数组,3个消费者和2个生产者。

    // 创建消费者线程。
    pthread_create(&c[0], nullptr, consumer, (void*)rq);
    pthread_create(&c[1], nullptr, consumer, (void*)rq);
    pthread_create(&c[2], nullptr, consumer, (void*)rq);

    // 创建生产者线程。
    pthread_create(&p[0], nullptr, productor, (void*)rq);
    pthread_create(&p[1], nullptr, productor, (void*)rq);

    // 等待所有线程完成。
    for(int i = 0; i < 3; i++) pthread_join(c[i], nullptr);
    for(int i = 0; i < 2; i++) pthread_join(p[i], nullptr);

    return 0; // 程序结束。
}

这段代码展示了如何使用前面定义的RingQueue类来创建一个多生产者-多消费者模型。在这个模型中,生产者生成随机数并将其放入环形队列,而消费者从队列中取出这些数字并处理它们

首先通过srand()设置随机数种子,以确保每次程序运行时生成的随机数序列不同。然后,它创建了一个RingQueue<int>对象,用于存储生产者线程生成的整数。

接着,代码创建了3个消费者线程和2个生产者线程。每个线程都被分配了一个工作函数:生产者调用productor函数,而消费者调用consumer函数。这些线程通过pthread_create函数创建,并将RingQueue对象作为参数传递给它们的工作函数。

最后,main函数使用pthread_join等待所有线程完成,以确保程序在所有线程都执行完毕后才退出。

温馨提示

感谢您对博主文章的关注与支持!如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C++编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索Linux、C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

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

相关文章

408计算机网络--基础概论

学习计算机网络走以前需要首先明白一个大的概念&#xff0c;计算机网络通常分为通信子网&#xff08;实现数据通信&#xff09;和资源子网&#xff08;实现资源共享/数据处理&#xff09;七层妖塔 计算机网络&#xff1a;是一个将分散的、具有独立功能的计算机系统&#xff0…

【Leetcode 2415】反转二叉树的奇数层 —— 回溯法 | BFS

2415. 反转二叉树的奇数层 给你一棵 完美 二叉树的根节点root&#xff0c;请你反转这棵树中每个 奇数 层的节点值。 例如&#xff0c;假设第 3 层的节点值是[2,1,3,4,7,11,29,18]&#xff0c;那么反转后它应该变成[18,29,11,7,4,3,1,2]。 反转后&#xff0c;返回树的根节点。…

【Redis,Java】Redis的两种序列化方式—nosql数据库

redis和mysql的区别&#xff1a; redis是属于nosql的数据库&#xff0c;而mysql是属于sql数据库&#xff0c;redis是属于nosql数据库。mysql是存储在磁盘中的&#xff0c;redis是存储在内存中的&#xff0c;所以redis的读取书读快。这里所说的redis代表nosql&#xff0c;而mysq…

【elk查日志 elastic(kibana)】

文章目录 概要具体的使用方式一&#xff1a;查找接口调用历史二&#xff1a;查找自己的打印日志三&#xff1a;查找错误日志 概要 每次查日志&#xff0c;我都需要别人帮我&#xff0c;时间长了总觉得不好意思&#xff0c;所以这次下定决心好好的梳理一下&#xff0c;怎么查日…

更改WordPress作者存档链接author和用户名插件Change Author Link Structure

WordPress作者存档链接默认情况为/author/Administrator&#xff08;用户名&#xff09;&#xff0c;为了防止用户名泄露&#xff0c;我们可以将其改为/author/1&#xff08;用户ID&#xff09;&#xff0c;具体操作可参考『如何将WordPress作者存档链接中的用户名改为昵称或ID…

【大模型 幻觉】CRAG:改进传统的 RAG,增加纠正机制提升生成文本的准确性

CRAG&#xff1a;改进传统的 RAG&#xff0c;增加纠正机制提升生成文本的准确性 提出背景CRAG 框架轻量级检索评估器大规模网络搜索分解再重组算法 提出背景 论文&#xff1a;https://arxiv.org/abs/2401.15884 代码&#xff1a;https://github.com/HuskyInSalt/CRAG 大型语言…

适合tiktok运营的云手机需要满足什么条件?

TikTok作为一款全球热门的社交媒体平台&#xff0c;具有无限的市场潜力。然而&#xff0c;卖家在运营过程中常常会面临到视频0播、账号被降权、限流等问题&#xff0c;甚至可能因为多人同时使用一个IP而导致封号的风险。为了规避这些问题&#xff0c;越来越多的卖家将目光投向了…

k8s学习(RKE+k8s+rancher2.x)成长系列之简配版环境搭建(四)之Helm及cert-manager安装

安装Helm(三台都安装) 下载helm安装包并加入执行目录 tar zxf helm-v3.2.4-linux-amd64.tar.gz cd linux-amd64 cp helm /usr/bin/ helm version添加rancher稳定版仓库(三台都安装) helm repo add rancher-stable http://rancher-mirror.oss-cn-beijing.aliyuncs.com/serve…

8.2 新特性 - 透明的读写分离

文章目录 前言1. 安装部署1.1 下载安装包1.2 MySQL Shell1.3 配置 MySQL 实例1.4 启动 ReplicaSet1.5 启动 8.2 Router 2. 测试路由总结 前言 MySQL 8.0 官方推出过一个高可用方案 ReplicaSet 主要由 Router、MySQL Shell、MySQL Server 三个组件组成。 MySQL Shell 负责管理…

qt-双臂SCARA机器人动画

qt-双臂SCARA机器人动画 一、演示效果二、核心程序三、下载链接 在Qt opengl中完成的双臂SCARA机器人的简单模拟。 一、演示效果 二、核心程序 #include "glwidget.h"#include <GL/glu.h>GLWidget::GLWidget(QWidget *parent) :QGLWidget(parent),pitch(30.…

Java集合框架-1

目录 List集合 常见方法 迭代器&#xff08;Iterator&#xff09; List集合特有方法 List 的特点 创建 List 遍历List Java集合框架是Java编程语言提供的各种数据结构和算法的实现。它提供了不同类型的集合类&#xff0c;如列表(List)、集(Set)、映射(Map)等&#xff0c…

iOS通过p12导出公钥失败

通过p12文件导出证书crt文件一直报invalid password错误。 adminJiaozl Downloads % openssl pkcs12 -in ce.p12 -clcerts -nokeys -out MyCertificate.crt Enter Import Password: Mac verify error: invalid password?解决&#xff1a; 在 https://developer.apple.com/acc…

MongoDB文档插入

文章目录 MongoDB文档插入对比增删改查文档插入 MongoDB写安全机制非确认式写入 MongoDB文档查询参数说明查询操作符比较查询操作符逻辑查询操作符元素查询操作符数组查询操作符 模糊查询区别:$regex操作符中的option选项 MongoDB游标介绍游标函数手动迭代游标示例游标介绍 Mon…

AMC8美国数学思维竞赛2000-2024年真题练一练,你能做对几道?

为帮助备考AMC8美国数学竞赛的孩子们了解比赛的题型&#xff0c;掌握相关的知识&#xff0c;我会陆续分享2000-2024年的比赛真题&#xff0c;每道题都有解析&#xff0c;帮助孩子们不但会做题&#xff0c;还掌握背后的知识&#xff0c;从而达到举一反三的效果&#xff0c;提升比…

QT-LCD模拟显示

QT-LCD模拟显示 一、演示效果二、关键程序三、下载链接 一、演示效果 二、关键程序 #include "lcd_widget.h" #include <QDebug> #include <QPainter>LCDWidget::LCDWidget(QWidget *parent) : QWidget(parent),display(nullptr), display_char_buffer(…

LeetCode JS专栏刷题笔记(二)

一、前言 LeetCode - JavaScript 专栏刷题笔记第二篇。 第一篇刷题笔记详见&#xff1a;LeetCode JS专栏刷题笔记&#xff08;一&#xff09; 二、算法题目 1. 复合函数 LeetCode地址&#xff1a;2629. 复合函数 请你编写一个函数&#xff0c;它接收一个函数数组 [f1, f2, …

Mac清理系统垃圾软件CleanMyMac X2025免费版本下载

嘿&#xff0c;Mac用户们&#xff0c;你是否经常感觉你的Mac就像是在背着一个沉重的包袱在跑步&#xff1f;是的&#xff0c;我在说那些堆积如山的系统垃圾。清理这些垃圾不仅可以让你的Mac跑得更快&#xff0c;还能让它“呼吸”更畅快。今天&#xff0c;让我们一起来探索如何M…

【医学大模型】MEDDM LLM-Executable CGT 结构化医学知识: 将临床指导树结构化,便于LLM理解和应用

MEDDM LLM-Executable CGT 结构化医学知识: 将临床指导树结构化&#xff0c;便于LLM理解和应用 提出背景对比传统医学大模型流程步骤临床指导树流程图识别临床决策支持系统 总结解决方案设计数据收集与处理系统实施临床决策支持 提出背景 论文&#xff1a;https://arxiv.org/p…

代码智能生成插件‘通义灵码‘使用指南

介绍 通义灵码&#xff0c;通义灵码&#xff0c;是阿里云出品的一款基于通义大模型的智能编码辅助工具&#xff0c;提供行级/函数级实时续写、自然语言生成代码、单元测试生成、代码优化、注释生成、代码解释、研发智能问答、异常报错排查等能力&#xff0c;并针对阿里云的云服…

相机图像质量研究(36)常见问题总结:编解码对成像的影响--块效应

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…