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

目录

一、单生产单消费

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_wait
    void P(sem_t &sem);
    // 封装系统调用sem_post
    void 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_wait
    void P(sem_t &sem);
    // 封装系统调用sem_post
    void 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;
}

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

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

相关文章

Linux下的WatchDog

看门狗&#x1f415; 看门狗简介 看门狗定时器&#xff08;Watchdog Timer&#xff09;是一种定时器&#xff0c;用于检测系统是否正常运行。如果系统在规定时间内没有向看门狗定时器发送复位信号&#xff0c;看门狗定时器就会产生复位信号&#xff0c;使系统复位。看门狗定时…

基于SpringBoot的速食零食商城+LW示例参考

1.项目介绍 功能模块&#xff1a;管理端&#xff08;用户管理、账号管理、商品分类管理、商品信息管理、订单管理等&#xff09;&#xff0c;用户端&#xff08;商品信息、登录注册、我的订单等&#xff09;技术栈&#xff1a;SpringBoot&#xff0c;thymeleaf&#xff0c;MyB…

springboot020基于Java的免税商品优选购物商城设计与实现

&#x1f345;点赞收藏关注 → 文档最下方联系方式领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345; 一 、设计说明 1…

认识物联网

新一代信息技术 物联网 物物相连的互联网&#xff0c;即物联网&#xff0c;又称传感器常见的传感器 • 温度传感器 • 压力传感器 • 声音传感器 • 02 • */08521 物联网概念 • 通过射频识别&#xff0c;红外传感器&#xff0c;全球定位系统GPS&#xff0c;激光扫描…

CODESYS可视化桌面屏保-动态气泡制作详细案例

#一个用于可视化(HMI)界面的动态屏保的详细制作案例程序# 前言: 在工控自动化设备上,为了防止由于人为误触发或操作引起的故障,通常在触摸屏(HMI)增加屏幕保护界面,然而随着PLC偏IT化的发展,在控制界面上的美观程度也逐渐向上位机或网页前端方面发展,本篇模仿Windows…

Java基础——反射

反射是框架设计的灵魂 &#xff08;使用的前提条件&#xff1a;必须先得到代表的字节码的Class&#xff0c;Class类用于表示.class文件&#xff08;字节码&#xff09;&#xff09; 翻译成人话就是&#xff1a;反射技术&#xff0c;指的是加载类的字节码到内存&#xff0c;并以…

Node.js——文件上传

文件上传 插件&#xff1a;formidable&#xff0c;地址&#xff1a;https://www.npmjs.com/package/formidable&#xff0c;参考里面with Express.js部分。 html部分截图参考&#xff1a; 用express-generator生成的示例代码&#xff1a; const formidable require(formi…

PCA9632笔记

个人学习笔记&#xff0c;有错漏。具体请以官方数据手册为准 I2C地址 PCA9632使用I2C通信&#xff0c;I2C设备地址固定 发出START后输出访问设备地址&#xff08;8bit版本地址固定&#xff09; 0x62&#xff08;7位地址&#xff09; 地址最后一位为1读 为0写 8位写地址 0xC4…

【算法】递归+回溯+剪枝:78.子集

目录 1、题目链接 2、题目 3、解法(回溯剪枝) 4、代码 1、题目链接 78.子集&#xff08;LeetCode&#xff09; 2、题目 3、解法(回溯剪枝) 思路&#xff1a; 枚举子集&#xff08;答案&#xff09;的第一个数选谁&#xff0c;第二个数选谁&#xff0c;第三个数选谁&#x…

HCIP(7)-边界网关协议BGP基本配置(对等体peer,宣告network,引入import)

边界网关协议&#xff08;Border Gateway Protocol&#xff0c;BGP&#xff09;是一种用来在路由选择域之间交换网络层可达性信息&#xff08;Network Layer Reachability Information&#xff0c;NLRI&#xff09;的路由选择协议。由于不同的管理机构分别控制着他们各自的路由…

基于python的机器学习(二)—— 使用Scikit-learn库

目录 一、样本及样本划分 1.1 划分样本的方法 1.1.1 train_test_split()函数 1.1.2 时间序列划分 1.1.3 交叉验证 二、导入或创建数据集 2.1 导入Sklearn自带的样本数据集 2.2 利用Sklearn生成随机的数据集 2.3 读入自己创建的数据集 2.3.1 用Python直接读取文本文件…

Webpack5常用配置

1、序言 Webpack属于构建工具&#xff0c;可以将开发者代码转化成浏览器能识别的代码&#xff0c;让开发者专注代码实现&#xff0c;不用过多关注浏览器兼容性问题。 Webpack常见功能&#xff1a; 模块打包&#xff1a;Webpack 可以将项目中的所有模块&#xff08;包括 JavaScr…

DFA算法实现敏感词过滤

DFA算法实现敏感词过滤 需求&#xff1a;检测一段文本中是否含有敏感词。 比如检测一段文本中是否含有&#xff1a;“滚蛋”&#xff0c;“滚蛋吧你”&#xff0c;“有病”&#xff0c; 可使用的方法有&#xff1a; 遍历敏感词&#xff0c;判断文本中是否含有这个敏感词。 …

索引基础篇

前言 通过本篇博客的学习&#xff0c;我希望大家可以了解到 “索引” 是为了提高数据的查询效率。 索引的介绍 索引是为了提高查询数据效率的数据结构 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构(有序)。在数据之外&#xff0c;数据库系统还维护着…

【设计模式系列】外观模式(十四)

一、什么是外观模式 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;其核心目的是为一个复杂的子系统提供一个简化的接口。这种模式通过定义一个外观类来封装子系统内复杂的操作&#xff0c;隐藏系统内部的复杂性&#xff0c;并向客户端提供…

哪些因素会影响 DC/DC 转换电路快速测试的性能?-纳米软件

DC/DC 转换电路在现代电子设备中起着至关重要的作用&#xff0c;其性能的快速准确测试对于确保电子系统的可靠性和稳定性至关重要。然而&#xff0c;有许多因素会影响 DC/DC 转换电路快速测试的性能。 电路复杂性和参数多样性 单片 DC/DC 转换器由于功能模块和参数复杂性&…

从0开始深度学习(25)——多输入多输出通道

之前我们都只研究了一个通道的情况&#xff08;二值图、灰度图&#xff09;&#xff0c;但实际情况中很多是彩色图像&#xff0c;即有标准的RGB三通道图片&#xff0c;本节将更深入地研究具有多输入和多输出通道的卷积核。 1 多输入通道 当输入包含多个通道时&#xff0c;需要…

2025天津市考8日报名,建议收藏好报名流程

天津市2025年招考2043名公务员公告 35周岁以下&#xff08;1988年11月至2006年11月期间出生&#xff09;&#xff0c;2025年应届硕士、博士研究生报考的&#xff0c;年龄可放宽到40周岁&#xff08;1983年11月以后出生&#xff09;&#xff1b; 报名时间&#xff1a;2024年11月…

25中海油笔试测评春招秋招校招暑期实习社招笔试入职测评行测题型微测网题型分享

中海油笔试一般采用线上机考的形式。考试时间为 120 分钟&#xff0c;满分 100 分。笔试内容主要包括思想素质测评和通用能力测评两个科目。以下是具体介绍&#xff1a; 1. 思想素质测评&#xff1a; ✅价值观&#xff1a;考察考生对工作、职业、企业等方面的价值观念和态度&…

使用Docker Compose构建多容器应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Docker Compose构建多容器应用 引言 Docker Compose 简介 安装 Docker Compose 创建基本配置 运行多容器应用 查看服务状态 …