【线程】基于环形队列的生产者消费者模型

ops/2025/2/4 11:36:40/

1 环形队列

环形队列采用数组来模拟,用取模运算来模拟环状特性。
在这里插入图片描述
1.如何判断环形队列为空或者为满?

  • 环形队列时,头和尾都指向同一个位置。
  • 环形队列时,头和尾也都指向同一个位置。

因此, 可以通过加计数器或者标记位来判满或者空,也可以预留一个空的位置,作为满的状态

1.1 生产者消费者中的环形队列

2.生产者和消费者什么时候会访问同一个位置?

1.环形队列为空的时候,生产者和消费者会访问同一个位置。
在这里插入图片描述
环形队列中,如果队列为空,那么队首和队尾的指针是指向同一个位置的。所以,生产者和消费者也会指向同一个位置。

2.环形队列为满的时候,生产者和消费者会访问同一个位置。
在这里插入图片描述
环形队列中,如果队列为满,那么队首和队尾的指针是指向同一个位置的。所以,生产者和消费者也会指向同一个位置。

其他任何时候,生产者和消费者访问的都是不同的区域。
在这里插入图片描述

3.环形队列中的数据需要注意什么?

1.消费者不能超过生产者。
在这里插入图片描述
也就是: 不能没有数据了还继续消费

2.生产者不能将消费者套圈。
在这里插入图片描述
也就是: 不能没有空间了还继续生产

2 设计信号量

生产者最在意的是空闲的空间个数
消费者最在意的是数据的个数

所以生产者每次在访问临界资源之前,需要先申请空间资源的信号量,申请成功就可以进行生产,否则就阻塞等待。

消费者在访问临界资源之前,需要申请数据资源的信号量,申请成功就可以消费数据,否则就阻塞等待。

空间资源信号量的申请由生产者进行,归还(V操作)由消费者进行,表示生产者可以生产数据。

数据资源信号量的申请有消费者进行,归还(V操作)由生产者进行,表示消费者可以进行消费.

4.空间资源信号如何设计?

最开始,生产者没有生产,所以空间资源是队列的大小(满的)

5.数据资源信号如何设计?

最开始,生产者没有生产,所以数据资源为0(空的)

2.1 生产者伪代码

productor_sem = 环形队列大小;P(productor_sem);//申请空间资源信号量
//申请成功,继续向下运行。
//申请失败,阻塞在申请处。.......//从事生产活动——把数据放入队列中V(comsumer_sem);//归还数据资源信号量

2.2 消费者伪代码

comsumer_sem = 0;P(comsumer_sem);//申请数据资源信号量
//申请成功,继续向下运行。
//申请失败,阻塞在申请处。.......//从事消费活动——从队列中消费数据V(proudctor_sem);//归还空间资源信号量

环形队列中,大部分情况下单生产和单消费是可以并发执行的,只有在满或者空的时候,才会有同步和互斥问题,同步和互斥是通过信号量来实现的。

3 代码

3.1 RingQueue.h

#include <iostream>
#include <vector>
#include <semaphore.h>
#include <pthread.h>//为什么基于阻塞队列的生产者消费者模型只需要一个锁 , 而基于环形队列生产者消费者模型需要两个锁?
//1.因为阻塞队列是对同一个队列的同一个位置进行操作,队列是共享资源,需要对整个队列加锁
//2.阻塞队列中,生产者和消费者访问的不是同一个下标,所以两者是互不干扰的,只需要用条件变量来唤醒阻塞
//但是生产者对生产者而言,是需要加锁的。消费者对消费者而言,是需要加锁的。template <class T>
class RingQueue
{static const int defaultnum = 5;
public://p操作,申请信号量,信号量--void p(sem_t& sem){sem_wait(&sem);}//v操作,释放信号量,信号量++void v(sem_t& sem){sem_post(&sem);}void Lock(pthread_mutex_t& mutex){pthread_mutex_lock(&mutex);}void UnLock(pthread_mutex_t& mutex){pthread_mutex_unlock(&mutex);}public:RingQueue(int cap = defaultnum):_ringqueue(cap),_cap(cap),_c_step(0),_p_step(0){//初始化信号量,给消费者初始的信号量为0,给生产者初始的信号量为cap(满)//因为最开始的时候没有商品,无法消费,只能生产sem_init(&_c_data_sem, 0, 0);sem_init(&_p_space_sem, 0, cap);pthread_mutex_init(&_c_mutex, nullptr);pthread_mutex_init(&_p_mutex, nullptr);}//生产商品void push(const T &in){   //1.申请信号量p(_p_space_sem);//2.消费者加锁Lock(_p_mutex);//3.进行生产_ringqueue[_p_step] = in;_p_step++;_p_step %= _cap;  //保证下标一直都在环形队列里面//4.解锁UnLock(_p_mutex);//5.释放信号量v(_c_data_sem);}void pop(T* out){//1.申请信号量p(_c_data_sem);//2.申请锁Lock(_c_mutex);//3.消费数据*out = _ringqueue[_c_step];++_c_step;_c_step %= _cap;//4.解锁UnLock(_c_mutex);//5.生产者信号量++v(_p_space_sem);  //消费完数据之后,生产者的信号量++}
private:std::vector<T> _ringqueue;int _cap;     //capacityint _c_step;  //消费者在环形队列中的下标int _p_step;  //生产者在环形队列中的下标sem_t _c_data_sem;  //消费者关注的数据资源sem_t _p_space_sem; //生产者关注的消费资源pthread_mutex_t _c_mutex;   //消费者锁pthread_mutex_t _p_mutex;   //生产者锁
};

3.1.1 生产商品

//生产商品void push(const T &in){   //1.申请信号量p(_p_space_sem);//2.消费者加锁Lock(_p_mutex);//3.进行生产_ringqueue[_p_step] = in;_p_step++;_p_step %= _cap;  //保证下标一直都在环形队列里面//4.解锁UnLock(_p_mutex);//5.释放信号量v(_c_data_sem);}

6.为什么要按照 申请信号量 → 加锁 → 解锁 → 释放信号量 的顺序来做?

如果先加锁再申请信号量,可能会出现以下这种情况:
加锁之后,发现没有空闲空间了,于是阻塞。而此时该线程还持有锁,就会导致死锁。.
如果先释放信号量再解锁,可能会出现以下这种情况:
释放信号量之后,消费者得知有可以消费的资源,于是开始消费。但是此时并没有解锁,因此生产者可能并没有完全完成生产,但是消费者已经开始消费。就会导致生产消费数据不一致的问题。.

3.1.2 消费商品

void pop(T* out){//1.申请信号量p(_c_data_sem);//2.申请锁Lock(_c_mutex);//3.消费数据*out = _ringqueue[_c_step];++_c_step;_c_step %= _cap;//4.解锁UnLock(_c_mutex);//5.生产者信号量++v(_p_space_sem);  //消费完数据之后,生产者的信号量++}

在这里插入图片描述


http://www.ppmy.cn/ops/155555.html

相关文章

SSM开发(九) mybatis多表查询(举例说明)

目录 一、背景 二、一对一查询 三、一对多查询 一、背景 用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户 mysql表设计: 二、一对一查询 一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户 实体: @Data public class Order {pr…

Node.js 的底层原理

Node.js 的底层原理 1. 事件驱动和非阻塞 I/O Node.js 基于 Chrome V8 引擎&#xff0c;使用 JavaScript 作为开发语言。它采用事件驱动和非阻塞 I/O 模型&#xff0c;使其轻量且高效。通过 libuv 库实现跨平台的异步 I/O&#xff0c;包括文件操作、网络请求等。 2. 单线程事…

openeuler 22.03 lts sp4 使用 cri-o 和 静态 pod 的方式部署 k8s-v1.32.0 高可用集群

前情提要 整篇文章会非常的长…可以选择性阅读,另外,这篇文章是自己学习使用的,用于生产,还请三思和斟酌 静态 pod 的部署方式和二进制部署的方式是差不多的,区别在于 master 组件的管理方式是 kubectl 还是 systemctl有 kubeadm 工具,为什么还要用静态 pod 的方式部署?…

UE PlayerController、AIController

19. APlayerController 定义和功能 定义&#xff1a;APlayerController是Unreal Engine中用于处理玩家输入并将其转化为游戏世界中的动作的类。它是连接玩家和游戏角色&#xff08;通常是Pawn&#xff09;之间的桥梁&#xff0c;负责接收输入并通过Possess方法控制Pawn。 功能…

如何在Windows、Linux和macOS上安装Rust并完成Hello World

如何在Windows、Linux和macOS上安装Rust并完成Hello World 如果你刚刚开始学习Rust&#xff0c;第一步就是安装Rust并运行你的第一个程序&#xff01;本文将详细介绍如何在Windows、Linux和macOS上安装Rust&#xff0c;并编写一个简单的“Hello, World!”程序。 1. 安装Rust …

【axios二次封装】

axios二次封装 安装封装使用 安装 pnpm add axios封装 // 进行axios二次封装&#xff1a;使用请求与响应拦截器 import axios from axios import { ElMessage } from element-plus//创建axios实例 const request axios.create({baseURL: import.meta.env.VITE_APP_BASE_API,…

Spring 面试题【每日20道】【其二】

1、Spring MVC 具体的工作原理&#xff1f; 中等 Spring MVC 是 Spring 框架的一部分&#xff0c;专门用于构建基于Java的Web应用程序。它采用模型-视图-控制器&#xff08;MVC&#xff09;架构模式&#xff0c;有助于分离应用程序的不同方面&#xff0c;如输入逻辑、业务逻辑…

人工智能学习(五)之机器学习逻辑回归算法

深入剖析机器学习逻辑回归算法 一、引言 在机器学习领域&#xff0c;逻辑回归是一种极为经典且应用广泛的算法。虽说名字里带有 “回归”&#xff0c;但它主要用于解决分类问题&#xff0c;在医学、金融、互联网等多个领域都发挥着关键作用。例如&#xff0c;在医学上辅助判断…