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

在这里插入图片描述

阅读导航

  • 引言
  • 一、生产者消费者模型
  • 二、环形队列简介
  • 三、基于环形队列的生产者消费者模型(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++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

S32 Design Studio PE工具配置Watch Dog

配置操作 在一个component下面可以创建多个看门狗&#xff0c;一般会有个限制&#xff0c;就是不能创建多个 看门狗比较简单&#xff0c;在configurations list里面新建软件看门狗&#xff0c;配置里面的名字、超时时间等配置即可。 代码对应 生成的代码在watchdog1.c和 wat…

CV论文--2024.2.19

1、Self-Play Fine-Tuning of Diffusion Models for Text-to-Image Generation 中文标题&#xff1a;自我对弈微调扩散模型&#xff0c;用于文本到图像生成 简介&#xff1a;在生成人工智能&#xff08;GenAI&#xff09;领域&#xff0c;微调扩散模型仍然是一个未被充分探索的…

搭建本地git仓库 gogs本地大家 CentOS搭建本地git仓库 CentOS部署gogs

运行环境 操作系统:CentOS7.8 64位 使用mysql5.7.44数据库 选用依赖 yum install vim wget unzip -y本文选择使用WLNMP集成环境 第一步 配置epel源(必须) yum install epel-release第二步 添加wlnmp源 这里选择自动脚本 curl -fsSL "https://sh.wlnmp.com/wlnmp…

算法模板 7.拓扑排序

拓扑排序 用来解决循环依赖相关问题&#xff01;&#xff01;&#xff01; 一个有向无环图一定存在一个拓扑序列&#xff01;一定存在至少一个入度为0的点 有向无环图也被称作拓扑图 先把入度为0的点压入队列&#xff0c;然后进行广度优先搜索&#xff08;找到队头&#xf…

遥感影像数据处理分析软件与ChatGPT集成、多光谱数据分析与实践、高光谱数据分析与实践

目录 第一章 遥感科学与AI基础 第二章 遥感影像数据处理分析软件与ChatGPT集成 第三章 多光谱数据分析与实践专题 第四章 高光谱分析与实践专题 更多应用 将最新的人工智能技术与实际的遥感应用相结合&#xff0c;提供不仅是理论上的&#xff0c;而且是适用和可靠的工具和…

【天锐绿盾】| 数据防泄漏软件——防止公司核心文件数据\资料外泄、泄露!

数据防泄漏软件 数据防泄漏&#xff08;DLP&#xff09;软件是一种专门设计用于保护企业和组织内部敏感信息的网络安全工具。 PC端&#xff1a;https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 它们通常包含以下核心功能&#xff1a; 文件加密…

2、windows环境下vscode开发c/c++环境配置(一)

前言&#xff1a;VSCode是微软出的一款轻量级编辑器&#xff0c;它本身只是一款文本编辑器而已&#xff0c;并不是一个集成开发环境(IDE)&#xff0c;几乎所有功能都是以插件扩展的形式所存在的。因此&#xff0c;我们想用它编程&#xff0c;不只是把vscode下载下来就行&#x…

C语言系列(所需基础:大学C语言及格)-3-字符串/ASCII码表

文章目录 一、字符串二、ASCII码表 一、字符串 用" "来定义字符串&#xff1a; #include <stdio.h>int main() {"";//空字符串"hkl";//由""定义的字符串return(0); }用数组来存储字符串&#xff0c;并打印&#xff1a; #incl…

深度学习系列——“试错”发展直觉

试错法以发展直觉&#xff1a;面对复杂的深度学习问题时&#xff0c;学习者可以通过不断尝试不同解决方案&#xff0c;并观察其对模型性能的影响&#xff0c;逐渐形成一套针对特定任务的有效策略。这些经验有助于提升对深度学习模型工作原理的直观理解。 那么试错法是如何发展直…

专修戴尔R730xd服务器闪电灯 心跳亮黄灯故障

2024年开年第二天接到一个用户反馈说他公司有一台DELL PowerEdge R730xd服务器春节前由于市电问题意外断电关机了&#xff0c;刚好碰上春节就没去开机了&#xff0c;今天工厂开工服务器通电发现开不了机&#xff0c;且机器过了一会后报了2个黄灯错误&#xff0c;如下图&#xf…

SpringCloud-基于Feign远程调用

Spring Cloud 是一个用于构建分布式系统的开发工具包&#xff0c;它提供了一系列的微服务组件&#xff0c;其中之一就是 Feign。Feign 是一种声明式的 Web 服务客户端&#xff0c;它简化了在 Spring Cloud 中进行远程调用的过程。本文将介绍如何在 Spring Cloud 中使用 Feign 进…

这里有几个0?

注意n最大取 #include<iostream> #define endl \n using namespace std; void solve(long long x) {int cnt 0, sum 0;while (x) {cnt x & 1;sum;x >> 1;}cout << sum-cnt << endl; } int main() {int t;long long n;cin >> t;while (t-…

【力扣白嫖日记】1890.2020年最后一次登录

前言 练习sql语句&#xff0c;所有题目来自于力扣&#xff08;https://leetcode.cn/problemset/database/&#xff09;的免费数据库练习题。 今日题目&#xff1a; 1890.2020年最后一次登录 表&#xff1a;Logins 列名类型user_idinttime_stampvarchar (user_id, time_sta…

智能计算: 最新进展、挑战和未来(九名院士、12位专家)

论文&#xff1a;Intelligent Computing: The Latest Advances, Challenges, and Future 论文地址&#xff1a;https://arxiv.org/abs/2211.11281 Abstract 计算是人类文明发展的一个重要推动力。近年来&#xff0c;我们见证了智能计算的出现&#xff0c;在大数据、人工智能和物…

【FastAPI】P3 请求与响应

目录 请求路径参数查询参数 响应JSON 响应文本响应返回 Pydantic 模型 在网络通讯中&#xff0c;请求&#xff08;Request&#xff09; 与 响应&#xff08;Response&#xff09; 扮演着至关重要的角色&#xff0c;它们构成了客户端与服务器间互动的根本理念。 请求&#xff0…

原创java开源项目发布maven全球中央仓库详细过程示范和遇到的问题解决办法

文章目录 java项目上传到maven全球中央仓库&#xff08;原创个人开源项目发布maven中央仓库详细过程示范&#xff09;需求背景第一步 注册sonatype账号第二步 登录sonatype账号并申请新建项目第三步 准备个人GPG数字签名并发布到ubuntu第四步 准备maven配置第五步 修改项目配置…

网络原理HTTP/HTTPS(2)

文章目录 HTTP响应状态码200 OK3xx 表示重定向4xx5xx状态码小结 HTTPSHTTPS的加密对称加密非对称加密 HTTP响应状态码 状态码表⽰访问⼀个⻚⾯的结果.(是访问成功,还是失败,还是其他的⼀些情况…).以下为常见的状态码. 200 OK 这是⼀个最常⻅的状态码,表⽰访问成功 2xx都表示…

hot100 -- 滑动窗口

目录 &#x1f33c;无重复字符 -- 最长子串 AC 滑动窗口&#xff08;桶&#xff09; &#x1f33c;所有字母异位词 AC 滑动窗口 桶 AC 滑动窗口&#xff08;优化&#xff09; &#x1f33c;无重复字符 -- 最长子串 一开始考虑用 BF暴力 或者 KMP 的&#xff0c;后来想…

安宝特AR汽车行业解决方案系列1-远程培训

在汽车行业中&#xff0c;AR技术的应用正悄然改变着整个产业链的运作方式&#xff0c;应用涵盖培训、汽修、汽车售后、PDI交付、质检以及汽车装配等&#xff0c;AR技术为多个环节都带来了前所未有的便利与效率提升。 安宝特AR将以系列推文的形式为读者逐一介绍在汽车行业中安宝…

【机器学习笔记】 15 机器学习项目流程

机器学习的一般步骤 数据清洗 数据清洗是指发现并纠正数据文件中可识别的错误的最后一道程序&#xff0c;包括检查数据一致性&#xff0c;处理无效值和缺失值等。与问卷审核不同&#xff0c;录入后的数据清理一般是由计算机而不是人工完成。 探索性数据分析(EDA 探索性数据…