当前位置: 首页 > news >正文

【操作系统】基于环形队列的生产消费模型

目录

一、单生产单消费

1.环形队列的实现

(1) void P(sem_t &sem);

(2) void V(sem_t &sem);

(3) RingQueue()

(4) ~RingQueue()

(5) void Push(const T &in);

(6) void Pop(T *out);

2.上层调用逻辑

二、多生产多消费

1.环形队列的实现

(1) RingQueue()

(2) ~RingQueue()

(3) void Push(const T &in);

(4) void Pop(T *out);

2.上层调用逻辑


这篇博客的重点在于代码实现,理论部分请看CSDN​​​​​​​

一、单生产单消费

1.环形队列的实现

单生产单消费的情况下,我们只需要维护生产者和消费者之间的互斥和同步关系即可

将环形队列封装成一个类:首先给出整体框架,接着会说明每一个类内函数的实现

#pragma once#include <iostream>
#include <vector>
#include <cassert>
#include <semaphore.h>// 环形队列的默认大小
static const int gcap = 5;// 设置成模版类型,队列中可以放任意类型的数据
template <class T>
class RingQueue
{
private:// 封装系统调用sem_waitvoid P(sem_t &sem);// 封装系统调用sem_postvoid V(sem_t &sem);public:RingQueue()~RingQueue()// 生产者向队列里放数据void Push(const T &in);// 消费者从队列中取数据void Pop(T *out);private:std::vector<T> _queue; // 数组模拟环形队列int _cap;              // 环形队列的容量sem_t _spaceSem;       // 生产者 -> 空间资源sem_t _dataSem;        // 消费者 -> 数据资源int _producerStep;     // 生产者的位置(数组下标)int _consumerStep;     // 消费者的位置(数组下标)
};

(1) void P(sem_t &sem);

封装系统调用sem_wait,便于在push和pop中使用

void P(sem_t &sem)
{int n = sem_wait(&sem);assert(n == 0);(void)n;
}

(2) void V(sem_t &sem);

封装系统调用sem_post,便于在push和pop中使用

void V(sem_t &sem)
{int n = sem_post(&sem);assert(n == 0);(void)n;
}

(3) RingQueue()

RingQueue(const int &cap = gcap): _queue(cap), _cap(cap)
{// 初始化信号量int n = sem_init(&_spaceSem, 0, _cap);assert(n == 0);n = sem_init(&_dataSem, 0, 0);assert(n == 0);// 初始化生产者和消费者的位置_productorStep = _consumerStep = 0;
}

(4) ~RingQueue()

~RingQueue()
{// 销毁信号量sem_destroy(&_spaceSem);sem_destroy(&_dataSem);
}

(5) void Push(const T &in);

生产者向环形队列里放数据

void Push(const T &in)
{// 生产者要了一个空间,空间信号量--P(_spaceSem);// 把数据放进队列_queue[_producerStep++] = in;// 维护环状结构_producerStep %= _cap;// 队列新增了数据,数据信号量++V(_dataSem);
}

(6) void Pop(T *out);

消费者从环形队列中取数据

void Pop(T *out)
{// 消费者拿了一个数据,数据信号量--P(_dataSem);// 把数据拿出队列*out = _queue[_consumerStep++];// 维护环状结构_consumerStep %= _cap;// 队列多出了空间,空间信号量++V(_spaceSem);
}

2.上层调用逻辑

#include "RingQueue.hpp"
#include <pthread.h>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>// 生产者线程
void *ProducerRoutine(void *rq)
{// 通过参数rq拿到环形队列RingQueue<int> *ringqueue = static_cast<RingQueue<int> *>(rq);// RingQueue<Task> *ringqueue = static_cast<RingQueue<Task> *>(rq);// 生产活动while (true){int data = rand() % 10 + 1; // 模拟构建任务ringqueue->Push(data); // 把任务放进环形队列std::cout << "生产完成,生产的数据是:" << data << std::endl;}
}// 消费者线程
void *ConsumerRoutine(void *rq)
{// 通过参数rq拿到环形队列RingQueue<int> *ringqueue = static_cast<RingQueue<int> *>(rq);// RingQueue<Task> *ringqueue = static_cast<RingQueue<Task> *>(rq);// 消费活动while (true){int data;ringqueue->Pop(&data); // 把任务从环形队列里取出// 处理任务std::cout << "消费完成,消费的数据是:" << data << std::endl;}
}int main()
{srand((unsigned int)time(nullptr) ^ getpid());// 创建一个环形队列(环形队列里可以是int也可以是任务Task)RingQueue<int> *rq = new RingQueue<int>();// RingQueue<Task> *rq = new RingQueue<Task>();// 单生产,单消费pthread_t p, c;pthread_create(p, nullptr, ProducerRoutine, rq);pthread_create(c, nullptr, ConsumerRoutine, rq);pthread_join(p, nullptr);pthread_join(c, nullptr);delete rq;return 0;
}

二、多生产多消费

1.环形队列的实现

上面的代码我们只维护了生产者和消费者之间的互斥和同步关系(三种关系中的一种)

为了实现多生产多消费,我们需要在去维护生产者和生产者,消费者和消费者之间的互斥关系

只要最终能保证:在任何时刻,只有一个生产者或一个消费者在临界区(环形队列)里 就行


为了维护 生产者和生产者/消费者和消费者 之间的互斥关系 -> 新增两把锁

① pthread_mutex_t _pmutex:保证只有一个生产者进入环形队列(多个生产者竞争出一个)

② pthread_mutex_t _cmutex:保证只有一个消费者进入环形队列(多个消费者竞争出一个)

总结: 维护生产者和消费者的关系 -> 信号量;维护生产者和生产者/消费者和消费者的关系 -> 锁

#pragma once#include <iostream>
#include <vector>
#include <cassert>
#include <semaphore.h>// 环形队列的默认大小
static const int gcap = 5;// 设置成模版类型,队列中可以放任意类型的数据
template <class T>
class RingQueue
{
private:// 封装系统调用sem_waitvoid P(sem_t &sem);// 封装系统调用sem_postvoid V(sem_t &sem);public:RingQueue()~RingQueue()// 生产者向队列里放数据void Push(const T &in);// 消费者从队列中取数据void Pop(T *out);private:std::vector<T> _queue;   // 数组模拟环形队列int _cap;                // 环形队列的容量sem_t _spaceSem;         // 生产者 -> 空间资源sem_t _dataSem;          // 消费者 -> 数据资源int _producerStep;       // 生产者的位置(数组下标)int _consumerStep;       // 消费者的位置(数组下标)pthread_mutex_t _pmutex; // 保证只有一个生产者进入队列pthread_mutex_t _cmutex; // 保证只有一个消费者进入队列
};

对系统调用sem_wait()和sem_post()的封装不变,其他四个函数需要做调整

(1) RingQueue()

RingQueue(const int &cap = gcap): _queue(cap), _cap(cap)
{// 初始化信号量int n = sem_init(&_spaceSem, 0, _cap);assert(n == 0);n = sem_init(&_dataSem, 0, 0);assert(n == 0);// 初始化生产者和消费者的位置_productorStep = _consumerStep = 0;// 初始化锁pthread_mutex_init(&_pmutex, nullptr);pthread_mutex_init(&_cmutex, nullptr);
}

(2) ~RingQueue()

~RingQueue()
{// 销毁信号量sem_destroy(&_spaceSem);sem_destroy(&_dataSem);// 销毁锁pthread_mutex_destroy(&_pmutex);pthread_mutex_destroy(&_cmutex);
}

(3) void Push(const T &in);

生产者向环形队列里放数据

void Push(const T &in)
{// 生产者要了一个空间,空间信号量--P(_spaceSem);// 选一个生产者放任务(加锁)pthread_mutex_lock(&_pmutex);// 把数据放进队列_queue[_producerStep++] = in;// 维护环状结构_producerStep %= _cap;// 放进队列后解锁pthread_mutex_unlock(&_pmutex);// 队列新增了数据,数据信号量++V(_dataSem);
}

(4) void Pop(T *out);

消费者从环形队列中取数据

void Pop(T *out)
{// 消费者拿了一个数据,数据信号量--P(_dataSem);// 选一个消费者拿任务(加锁)pthread_mutex_unlock(&_cmutex);// 把数据拿出队列*out = _queue[_consumerStep++];// 维护环状结构_consumerStep %= _cap;// 拿出队列后解锁pthread_mutex_unlock(&_cmutex);// 队列多出了空间,空间信号量++V(_spaceSem);
}

2.上层调用逻辑

#include "RingQueue.hpp"
#include "Task.hpp"
#include <pthread.h>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>// 生产者线程
void *ProducerRoutine(void *rq)
{// 通过参数rq拿到环形队列RingQueue<int> *ringqueue = static_cast<RingQueue<int> *>(rq);// RingQueue<Task> *ringqueue = static_cast<RingQueue<Task> *>(rq);// 生产活动while (true){int data = rand() % 10 + 1; // 模拟构建任务ringqueue->Push(data); // 把任务放进环形队列std::cout << "生产完成,生产的数据是:" << data << std::endl;}
}// 消费者线程
void *ConsumerRoutine(void *rq)
{// 通过参数rq拿到环形队列RingQueue<int> *ringqueue = static_cast<RingQueue<int> *>(rq);// RingQueue<Task> *ringqueue = static_cast<RingQueue<Task> *>(rq);// 消费活动while (true){int data;ringqueue->Pop(&data); // 把任务从环形队列里取出// 处理任务std::cout << "消费完成,消费的数据是:" << data << std::endl;}
}int main()
{srand((unsigned int)time(nullptr) ^ getpid());// 创建一个环形队列(环形队列里可以是int也可以是任务Task)RingQueue<int> *rq = new RingQueue<int>();// RingQueue<Task> *rq = new RingQueue<Task>();// 多生产,多消费pthread_t p[4], c[8];for (int i = 0; i < 4; i++)pthread_create(p + i, nullptr, ProducerRoutine, rq);for (int i = 0; i < 8; i++)pthread_create(c + i, nullptr, ConsumerRoutine, rq);for (int i = 0; i < 4; i++)pthread_join(p[i], nullptr);for (int i = 0; i < 8; i++)pthread_join(c[i], nullptr);delete rq;return 0;
}

http://www.mrgr.cn/news/65644.html

相关文章:

  • 关于Linux系统调试和性能优化技巧有哪些?
  • YOLOv10改进策略【注意力机制篇】| 2024 PPA 并行补丁感知注意模块,提高小目标关注度
  • 报错:npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。
  • 测试和实施面试题收集
  • 开源OCR免费助力法律文档数字化,提升文档管理效率
  • [ 问题解决篇 ] 解决远程桌面安全登录框的问题
  • 堆heap的讨论、习题与代码
  • 架构师考试系列(8)论文专题:信息系统安全设计
  • 探索 Intersection Observer API:提升网页性能的新途径
  • 主体Subject和客体Object-西方哲学的思维方式
  • mysql笔记-索引
  • 游游的游戏大礼包
  • windows完结---清风
  • 数据结构---自定义动态数组
  • 从零开发操作系统-为什么磁盘的扇区为 512 byte
  • PMP-人
  • 泛微开发修炼之旅--52关于ecology首页待办修改源码位置记录
  • C#:强大而优雅的编程语言
  • 书签管理工具使用技巧
  • H265编码丢帧问题分析
  • Java-I/O框架10:File类、文件操作
  • 关于LIMS实验室管理系统常见的几个误区
  • 多个锚点定位时的锚点优选方法(附公式和MATLAB代码讲解)
  • CSP 2024 入门级第二轮 CSP-J 2024 复赛 第一题 扑克牌
  • 路径跟踪之导航向量场(三)——无奇异点导航向量场
  • 弹性布局flex-direction