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

目录

一、单生产单消费

1.阻塞队列的实现

(1) BlockQueue()

(2) ~BlockQueue()

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

(4) void pop(T *out);

2.上层调用逻辑

二、多生产多消费

1.阻塞队列的实现

2.上层调用逻辑


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

一、单生产单消费

1.阻塞队列的实现

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

#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>

// 阻塞队列默认大小
const int gmaxcap = 5;

// 设置成模版类型
template <class T>
class BlockQueue
{
public:
    BlockQueue()
    ~BlockQueue()

    // 生产者向队列里放数据
    void push(const T &in);
    // 消费者从队列中取数据
    void pop(T *out);

private:
    // 判断队列是否为空
    bool is_empty() { return _q.empty(); }
    // 判断队列是否为满
    bool is_full() { return _q.size() == _maxcap; }

private:
    std::queue<T> _q;      // 阻塞队列
    int _maxcap;           // 队列中元素的上限
    pthread_mutex_t _mutex;// 锁(保证临界资源的安全)
    pthread_cond_t _pcond; // 生产者对应的条件变量
    pthread_cond_t _ccond; // 消费者对应的条件变量
};

① 将阻塞队列设置成模版类型,未来就可以往阻塞队列中放任意类型的数据

② pthread_mutex_t _mutex; ->  锁:保证访问阻塞队列是互斥的

③ pthread_cond_t _pcond; -> 生产者对应的条件变量(队列为满 让生产者休眠)

④ pthread_cond_t _ccond; -> 消费者对应的条件变量(队列为空 让消费者休眠)

(1) BlockQueue()

BlockQueue(const int &maxcap = gmaxcap)
    : _maxcap(maxcap)
{
    pthread_mutex_init(&_mutex, nullptr);
    pthread_cond_init(&_pcond, nullptr);
    pthread_cond_init(&_ccond, nullptr);
}

(2) ~BlockQueue()

~BlockQueue()
{
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_pcond);
    pthread_cond_destroy(&_ccond);
}

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

void push(const T &in)
{
    // 加锁访问阻塞队列
    pthread_mutex_lock(&_mutex);

    // 1.判断:队列为满 让生产者休眠
    while (is_full())// 细节2:充当条件判断的语法必须是while,不能用if
    {
        // 生产条件不满足,无法生产,让生产者进行等待
        pthread_cond_wait(&_pcond, &_mutex);// 细节1:第二个参数是正在使用的互斥锁
    }
    // 2.走到这里一定没有满(可以生产)
    _q.push(in);
    // 3.绝对能保证,阻塞队列里面一定有数据
    // 唤醒一个消费者 让他来消费
    pthread_cond_signal(&_ccond);// 细节3:可以放在临界区内部,也可以放在外部

    pthread_mutex_unlock(&_mutex);
}

① 细节1:pthread_cond_wait()函数的第二个参数,必须是我们正在使用的互斥锁!

② 细节2:充当条件判断的语法必须是while,不能用if -> 预防伪唤醒情况的发生

③ 细节3: pthread cond signal()函数 可以放在临界区内部,也可以放在临界区外部

(4) void pop(T *out);

void pop(T *out)
{
    pthread_mutex_lock(&_mutex);

    // 1.判断:队列为空 让消费者休眠
    while (is_empty())
    {
        // 消费条件不满足,无法消费,让消费者进行等待
        pthread_cond_wait(&_ccond, &_mutex);
    }
    // 2.走到这里一定不为空(可以消费)
    *out = _q.front();
    _q.pop();
    // 3.绝对能保证,阻塞列里面至少有一个空位
    // 唤醒一个生产者 让他来生产
    pthread_cond_signal(&_pcond);

    pthread_mutex_unlock(&_mutex);
}

2.上层调用逻辑

#include "BlockQueue.hpp"
#include <ctime>
#include <unistd.h>

// 消费者线程
void *consumer(void *bq_)
{
    // 首先要通过参数bq_拿到阻塞队列
    BlockQueues<int> *bq = static_cast<BlockQueues<int> *>(bq_);
    
    // 消费活动
    while (true)
    {
        int data;
        bq->pop(&data) // 把任务从进阻塞队列里取出
        // 处理任务
        std::cout << "消费的数据: " << data << std::endl;
    }
    return nullptr;
}

// 生产者线程
void *producer(void *bq_)
{
    // 首先要通过参数bq_拿到阻塞队列
    BlockQueues<int> *bq = static_cast<BlockQueues<int> *>(bq_);
    
    // 生产活动
    while (true)
    {
        int data = rand() % 10 + 1 // 模拟构建任务
        bq->push(); // 把任务放进阻塞队列
        std::cout << "生产的数据: " << data << std::endl;
    }
    return nullptr;
}

int main()
{
    srand((unsigned long)time(nullptr) ^ getpid());

    // 创建一个阻塞队列
    BlockQueues<int> *bq = new BlockQueues<int>();

    pthread_t c, p;
    pthread_create(&c, nullptr, consumer, (void *)bq);
    pthread_create(&p, nullptr, producer, (void *)bq);

    pthread_join(c, nullptr);
    pthread_join(p, nullptr);

    delete bq;
    return 0;
}

二、多生产多消费

1.阻塞队列的实现

多生产多消费阻塞队列和上面实现的单生产单消费的一样,原因是什么?

多个生产者进入到push()时要竞争这把锁 -> 永远只有一个生产者能进到阻塞队列

多个消费者进入到pop()时要竞争这把锁 -> 所以永远只有一个消费者能进到阻塞队列

因为有锁的存在,任何一个时刻只能有一个执行流在阻塞队列里push()/pop()数据

所以不管外面有多少线程,真正能进入阻塞队列里生产或消费的线程永远只有一个

2.上层调用逻辑

逻辑上没有任何差别,只需要多创建几个线程即可

#include "BlockQueue.hpp"
#include <ctime>
#include <unistd.h>

// 消费者线程
void *consumer(void *bq_)
{
    // 首先要通过参数bq_拿到阻塞队列
    BlockQueues<int> *bq = static_cast<BlockQueues<int> *>(bq_);
    
    // 消费活动
    while (true)
    {
        int data;
        bq->pop(&data) // 把任务从进阻塞队列里取出
        // 处理任务
        std::cout << "消费的数据: " << data << std::endl;
    }
    return nullptr;
}

// 生产者线程
void *producer(void *bq_)
{
    // 首先要通过参数bq_拿到阻塞队列
    BlockQueues<int> *bq = static_cast<BlockQueues<int> *>(bq_);
    
    // 生产活动
    while (true)
    {
        int data = rand() % 10 + 1 // 模拟构建任务
        bq->push(); // 把任务放进阻塞队列
        std::cout << "生产的数据: " << data << std::endl;
    }
    return nullptr;
}

int main()
{
    srand((unsigned long)time(nullptr) ^ getpid());

    // 创建一个阻塞队列
    BlockQueues<int> *bq = new BlockQueues<int>();

    // 创建多个线程
    pthread_t c[2], p[3];
    pthread_create(c, nullptr, consumer, (void *)bq);
    pthread_create(c+1, nullptr, consumer, (void *)bq);
    pthread_create(p, nullptr, producer, (void *)bq);
    pthread_create(p+1, nullptr, producer, (void *)bq);
    pthread_create(p+2, nullptr, producer, (void *)bq);

    pthread_join(c[0], nullptr);
    pthread_join(c[1], nullptr);
    pthread_join(p[0], nullptr);
    pthread_join(p[1], nullptr);
    pthread_join(p[2], nullptr);

    delete bq;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值