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

目录

一、单生产单消费

1.环形队列的实现

[(1) void P(sem_t &sem);](#(1) void P(sem_t &sem);)

[(2) void V(sem_t &sem);](#(2) void V(sem_t &sem);)

[(3) RingQueue()](#(3) RingQueue())

[(4) ~RingQueue()](#(4) ~RingQueue())

[(5) void Push(const T &in);](#(5) void Push(const T &in);)

[(6) void Pop(T *out);](#(6) void Pop(T *out);)

2.上层调用逻辑

二、多生产多消费

1.环形队列的实现

[(1) RingQueue()](#(1) RingQueue())

[(2) ~RingQueue()](#(2) ~RingQueue())

[(3) void Push(const T &in);](#(3) void Push(const T &in);)

[(4) void Pop(T *out);](#(4) void Pop(T *out);)

2.上层调用逻辑


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

一、单生产单消费

1.环形队列的实现

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

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

cpp 复制代码
#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中使用

cpp 复制代码
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中使用

cpp 复制代码
void V(sem_t &sem)
{
    int n = sem_post(&sem);
    assert(n == 0);
    (void)n;
}

(3) RingQueue()

cpp 复制代码
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()

cpp 复制代码
~RingQueue()
{
    // 销毁信号量
    sem_destroy(&_spaceSem);
    sem_destroy(&_dataSem);
}

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

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

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

(6) void Pop(T *out);

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

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

2.上层调用逻辑

cpp 复制代码
#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:保证只有一个消费者进入环形队列(多个消费者竞争出一个)

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

cpp 复制代码
#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()

cpp 复制代码
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()

cpp 复制代码
~RingQueue()
{
    // 销毁信号量
    sem_destroy(&_spaceSem);
    sem_destroy(&_dataSem);

    // 销毁锁
    pthread_mutex_destroy(&_pmutex);
    pthread_mutex_destroy(&_cmutex);
}

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

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

cpp 复制代码
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);

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

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

2.上层调用逻辑

cpp 复制代码
#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;
}
相关推荐
爱写代码的小朋友17 小时前
基于多约束遗传算法的中小学排座位优化模型研究
linux·人工智能·算法
天问一17 小时前
router路由类型和使用方法
开发语言·javascript·ecmascript
JAVA面经实录91717 小时前
Java多线程并发高频面试100题(完整版·含答案·背诵版)
java·开发语言·面试
无限进步_17 小时前
C++异常机制:抛出、捕获与栈展开
开发语言·c++·安全
小白学大数据17 小时前
深度探索:Python 爬虫实现豆瓣音乐全站采集
开发语言·爬虫·python·数据分析
用户67570498850217 小时前
Celery 太重了?这可能是你一直在找的 asyncio 任务队列
后端·python·消息队列
Cloud_Shy61817 小时前
Python 数据分析基础入门:《Excel Python:飞速搞定数据分析与处理》学习笔记系列(第十一章 Python 包跟踪器 下篇)
前端·后端·python·数据分析·excel
Xin_ye1008617 小时前
C# 零基础到精通教程 - 第八章:面向对象编程(进阶)——继承与多态
开发语言·c#
m0_7488394917 小时前
R包grafify:简单操作实现高效统计绘图
开发语言·r语言
王老师青少年编程17 小时前
csp信奥赛C++高频考点专项训练之前缀和&差分 --【一维前缀和】:宝石串
c++·前缀和·csp·高频考点·信奥赛·宝石串