【Linux】—— 基于阻塞队列的生产消费者模型

本文介绍了生产者消费者模型的基本概念及其实现原理,探讨了如何利用阻塞队列实现生产者与消费者之间的解耦,同时提供了具体的代码实现案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在我们介绍今天的内容之前我们先了解一些相关的概念
Linux线程基本概念
Linux线程控制
Linux线程互斥
Linux线程同步

接下来我们进入今天的主题生产者消费者模型

生产者消费者模型

生产者消费者模型概念
  • 321原则:3种关系,2类角色,一个交易场所
  • 3种关系是:生产者与生产者之间的关系,消费者与消费者之间的关系,生产者与消费者之间的关系
  • 2类角色是:一类为生产者,一类为消费者
  • 一个交易场所:其实简单来说就是一块物理内存,今天我们要讲的交易场所是阻塞队列
为什么要使用生产者消费者模型
  • 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题
  • 生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力
  • 简单来说阻塞队列就是用来给生产者和消费者解耦的
生产消费者模型的优点
  • 解耦,就是让生产者和消费者之间的关联性降低
  • 支持并发
  • 支持忙闲不匀
    生产者消费者的关系
基于阻塞队列的生产者消费者模型
  • BlockingQueue 在多线程编程中 阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构
  • 与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;
  • 当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)
生产者消费者模型实现代码
  • cp.hpp //头文件和相关实现文件
#ifndef __CP__HPP__
#define __CP__HPP__

#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
using namespace std;

class BlockQueue{
    private:
        queue<int> q;
        int cap;//队列的容量
        pthread_mutex_t lock;   //互斥锁
        pthread_cond_t full;  //条件变量,判断队列是否为满
        pthread_cond_t empty;

        void LockQueue()
        {
            pthread_mutex_lock(&lock);  //给队列加锁
        }

        void UnlockQueue()
        {
            pthread_mutex_unlock(&lock);//给队列解锁
        }

        bool QueueIsFull()
        {
            return q.size() == cap;
        }
        bool QueueIsEmpty()
        {
            return q.size() == 0;
        }
        void SignalConsumer()
        {
            pthread_cond_signal(&empty);//唤醒在empty的条件变量下等待的消费者
        }
        void ConsumerWait()
        {
            pthread_cond_wait(&empty,&lock);//队列为空,消费者开始等待并释放锁
        }
        void SignalProduct()
        {
            pthread_cond_signal(&full);//唤醒在full条件变量下等待的生产者
        }
        void ProductWait()
        {
            pthread_cond_wait(&full,&lock);//队列为full,生产者开始等待并释放锁
        }

    public:
        BlockQueue(int cap_ = 32):cap(cap_)//队列初始化
        {
            pthread_mutex_init(&lock,NULL);
            pthread_cond_init(&full,NULL);
            pthread_cond_init(&empty,NULL);
        }
        //生产者生产数据
        void PushData(const int& in)
        {
            LockQueue();
            while(QueueIsFull()){
                SignalConsumer();//若队列满了则通知消费者消费
                ProductWait();//生产者停止生产
            }
            //队列不为满则生产者可以生产product
            q.push(in);//将数据写进队列
            SignalConsumer();//一旦有数据就唤醒消费者消费
            UnlockQueue();
        }
        //消费者消费数据
        void PopData(int& out)
        {
            LockQueue();
            while(QueueIsEmpty()){
                SignalProduct();//通知生产者生产
                ConsumerWait();//消费者停止消费
            }
            //队列不为空,消费者可以消费
            //consumer
            out = q.front();//将队列头部数据保存下来
            q.pop();//删除该数据
            SignalProduct();//一旦有空间就通知生产者生产
            UnlockQueue();
        }
        //析构函数,清理资源
        ~BlockQueue()
        {
            pthread_mutex_destroy(&lock);
            pthread_cond_destroy(&full);
            pthread_cond_destroy(&empty);
        }
};

#endif
  • cp.cc //主函数所在文件
#include <time.h>
#include "cp.hpp"

void *consumer(void *arg)//arg中存放的是我们创建的阻塞队列的首地址
{
    int Data;
    BlockQueue* bq = (BlockQueue*)arg;
    for(;;){
        bq->PopData(Data);
        cout <<"Consumer Data: "<< Data << endl;
    }
}
void *product(void *arg)
{
    BlockQueue* bq = (BlockQueue*)arg;
    for(;;){
        int data = rand() % 100 + 1;
        bq->PushData(data);
        cout << "producter data: " << data << endl;
        sleep(1);
    }
}

int main()
{
    srand((unsigned long)time(NULL));//生成随机数
    BlockQueue bq(6);//阻塞队列

    pthread_t c,p;//创建两个线程c为消费者,p为生产者
    pthread_create(&c,NULL,consumer,(void*)&bq);//让生产者和消费者都看到该阻塞队列
    pthread_create(&p,NULL,product,(void*)&bq);


    pthread_join(c,NULL);
    pthread_join(p,NULL);

    return 0;
}
  • Makefile //自动化构建工具
cp:cp.cc
	g++ -o $@ $^ -lpthread
.PHONY:clean
clean:
	rm -f cp
### Linux 环境下信号量队列的使用与实现 #### 1. 信号量的基本概念 信号量是一种用于解决多线程或多进程之间同步和互斥问题的重要工具。在 Linux 中,信号量分为两种主要类型:**互斥信号量(mutex semaphore)** 和 **计数信号量(counting semaphore)**[^1]。 - **互斥信号量**主要用于保护临界区,确保某一时刻只有一个线程或进程能够访问特定资源。 - **计数信号量**则允许多个线程或进程按一定条件竞争资源,其核心是一个整数值,表示可用资源的数量。 #### 2. 信号量队列的作用 当某个进程尝试获取一个已经被占用的信号量时,该进程会被挂起并加入到信号量对应的等待队列中。一旦持有信号量的进程释放了信号量,内核会从等待队列中唤醒下一个合适的进程继续运行。 这种机制的核心在于维护了一个有序的等待列表,称为**信号量队列**。通过这种方式,Linux 实现了高效的任务调度和资源共享管理。 #### 3. 信号量队列的具体实现 以下是基于 Linux 的信号量队列的主要实现细节: ##### (1)数据结构定义 Linux 内核中使用的信号量由 `struct semaphore` 定义,其中包含了重要的字段来描述信号量的状态和关联的等待队列: ```c struct semaphore { spinlock_t lock; unsigned int count; /* 计数器 */ struct list_head wait_list; /* 等待队列头节点 */ }; ``` - `count`: 表示当前可用资源数量。对于互斥信号量而言,初始值通常为 1;而对于计数信号量,则可以根据实际需求设置不同的初值。 - `wait_list`: 是一个双向链表,存储所有因无法立即获得信号量而进入睡眠状态的任务。 ##### (2)初始化过程 创建一个新的信号量对象之前需要对其进行初始化操作。可以通过调用函数 `sema_init()` 来完成这一工作: ```c void sema_init(struct semaphore *sem, int val); ``` 参数说明如下: - `sem`: 待初始化的目标信号量指针; - `val`: 初始计数值。 例如,下面代码片段展示了如何声明并初始化一个互斥锁类型的信号量: ```c #include <linux/semaphore.h> struct semaphore my_mutex; // 初始化互斥信号量 sema_init(&my_mutex, 1); // 设置初始值为1 ``` ##### (3)获取信号量 要请求占有某信号量,可利用宏 `down()` 或更安全版本 `down_interruptible()` 函数: ```c int down_interruptible(struct semaphore *sem); ``` 如果目标信号量不可用(即 `count == 0`),调用方将被阻塞直到满足条件为止。返回值指示是否成功取得锁定权柄——零代表正常结束,负错误码意味着中断发生或其他异常情况。 对应地,在退出临界区域之后应当及时释放所持信号量使用权,这可通过上调方法达成目的: ```c void up(struct semaphore *sem); ``` 上述动作实际上增加了内部计数变量,并激活可能存在的首个候补者使其恢复活动态。 #### 4. 应用场景举例 假设存在一组生产者消费者模型的应用场合,它们共同作用于同一个缓冲池之上。为了避免冲突现象的发生,我们可以引入一对相互配合工作的二元型信号量分别监控空闲槽位数目以及已填充项目总量变化趋势[^4]: ```c struct semaphore empty_slots; // 控制剩余空间大小 struct semaphore filled_items; // 追踪现存有效条目统计 /* 生产者的逻辑部分摘录 */ produce_item(item) { down(&empty_slots); // 尝试减少空白位置指标 insert_into_buffer(item); // 插入新产生的实体至共享容器里边去 up(&filled_items); // 增加已完成制品计量单位 } /* 消费者的处理流程示意 */ consume_item() { item result; down(&filled_items); // 验证是否有东西可供提取出来 remove_from_buffer(result);// 取走指定成员项实例化局部副本 up(&empty_slots); // 提供额外容量给后续制造环节使用 process_result(result); // 对所得成果进一步加工处置 } ``` 在此范例当中,每当有一件商品生成完毕后都会相应增加填满物事标记的同时削减闲置场所额度;反之亦然,取走一件成品也会重新腾出一处容纳地点机会供给上游工序循环再利用之需。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值