小工具知识网

当前位置: 首页 > 行业知识 > 【Linux | C++ 】基于环形队列的多生产者多消费者模型(Linux系统下C++ 代码模拟实现)

行业知识

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

2026-10-21 00:47:43 来源:互联网转载

阅读导航

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

          引言

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

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

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

          一、生产者消费者模型

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

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

          二、环形队列简介

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

          主要特点包括:

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

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

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

            ⭕Makefile文件

            ring_queue:testMain.ccg++ -o $@ $^ -std=c++11 -lpthread.PHONY:cleanclean:rm -f ring_queue

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

            ⭕ . h 头文件

            ✅sem.hpp

            // 防止头文件重复包含的预处理指令。#ifndef _SEM_HPP_#define _SEM_HPP_// 引入输入输出流库,虽然在此代码中未直接使用,可能为后续扩展预留。#include // 引入POSIX信号量的头文件。#include // 定义一个类 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 #include #include #include "sem.hpp"// 定义一个全局常量作为队列的默认大小。const int g_default_num = 5;// 定义一个模板类RingQueue,用于实现环形队列。templateclass 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 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_来控制队列的空间和数据,确保生产者不会在队列满时添加元素,消费者不会在队列空时尝试取出元素。同时,通过两个互斥锁clock和plock分别保护消费者和生产者的操作,防止并发环境下的数据竞争问题。这样的设计使得RingQueue既能高效地管理数据,又能保证线程安全。

            ⭕ . cpp 文件

            ✅testMain.cpp

            // 包含RingQueue类的头文件。#include "ringQueue.hpp"#include  // 包含标准库,用于rand()等函数。#include    // 用于time()函数。#include  // 包含类型定义,例如pid_t。#include     // 包含各种常量和类型,并声明了各种函数,例如sleep()和getpid()。// 消费者线程的工作函数。void *consumer(void *args){    RingQueue *rq = (RingQueue *)args; // 将传入的参数转换为RingQueue指针。    while(true)    {        sleep(1); // 休眠1秒,模拟处理时间。        int x;        rq->pop(&x); // 从环形队列中取出一个元素。        // 打印消费信息,包括消费的值和当前线程ID。        std::cout     RingQueue        int x = rand() % 100 + 1; // 生成一个1到100之间的随机数。        // 打印生产信息,包括生产的值和当前线程ID。        std::cout     srand((uint64_t)time(nullptr) ^ getpid()); // 设置随机数种子,确保每次运行结果不同。    RingQueue

上一篇:服务器不花钱怎么开?

下一篇:异地就医备案后怎么会原地就医(异地就医备案转回原地就医怎么办)