_Linux多线程--生产者消费者模型篇

1. 为何要使用生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

  • 例子:
    就好比一个正在运行的网络程序;我们拿数据需要时间,我们处理数据也需要时间;那么当没数据时,另一个处理数据线程不就一直等吗?这就有点浪费效率了。那么中间有个缓存,就可以大大提高效率了。

生产者消费者模型优点

  • 解耦
  • 支持并发
  • 支持忙闲不均

在这里插入图片描述

  • 三种关系:
    • 生产者和生成者(互斥关系、竞争)
    • 消费者和消费者(互斥关系、竞争)
    • 生产者和消费者(互斥、同步竞争)
  • 二种角色: 生产者/消费者
  • 一个交易场所:超市

2. 基于BlockingQueue的生产者消费者模型

BlockingQueue

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

在这里插入图片描述
—(图片摘于相关教材资料)

3. C++ queue模拟阻塞队列的生产消费模型

条件变量使用规范

  • 等待条件代码
pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);
  • 给条件发送信号代码
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
  • 例子:
    在这里插入图片描述
  • 注意:
  • pthread_cond_wait第二个参数是一个锁,当成功调用wait之后,传入的锁,会被自动释放!
  • 当我被唤醒时,我从哪里醒来呢??
    • 从哪里阻塞挂起,就从哪里唤醒, 被唤醒的时候,我们还是在临界区被唤醒的
    • 当我们被唤醒的时候,pthread_cond_wait,会自动帮助我们线程获取锁
  • pthread_cond_wait: 但是只要是一个函数,就可能调用失败 && pthread_cond_wait: 可能存在 伪唤醒 的情况
    • 不用if判断;用while在这里插入图片描述

简单测试

1. BlockQueue (缓存–超市)

#pragma once

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

#define gDefaultCap 5
template <class T>
class BlockQueue
{
    bool isQueueEmpty()
    {
        return _bq.size() == 0;
    }
    bool isQueueFull()
    {
        return _bq.size() == _capacity;
    }

public:
    BlockQueue(int capacity = gDefaultCap) : _capacity(capacity)
    {
        pthread_mutex_init(&_mtx, nullptr);
        pthread_cond_init(&_Empty, nullptr);
        pthread_cond_init(&_Full, nullptr);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_Empty);
        pthread_cond_destroy(&_Full);
    }
    void push(const T &in) // 生产者
    {
        pthread_mutex_lock(&_mtx);
        while (isQueueFull())
            pthread_cond_wait(&_Full, &_mtx); // 检测临界资源条件
        // 数据满了; 等待消费者消费数据
        _bq.push(in); // 生产者放数据
        //if(_bq.size() >= _capacity/2) pthread_cond_signal(&_Empty); //数据积累到>=一半时再发送
        pthread_cond_signal(&_Empty); // 唤醒; 通知消费者,可以消费了
        pthread_mutex_unlock(&_mtx);
    }
    void pop(T *out) // 消费者
    {
        pthread_mutex_lock(&_mtx);
        while (isQueueEmpty())
            pthread_cond_wait(&_Empty, &_mtx); // 检测临界资源条件
        // 数据为空; 等待生产者生产数据
        *out = _bq.front();
        _bq.pop();           // 消费者消费
        pthread_cond_signal(&_Full); // 唤醒; 通知生产者,可以生成了了
        pthread_mutex_unlock(&_mtx);
    }

public:
    std::queue<T> _bq;     // 阻塞队列
    int _capacity;         // 容量上限
    pthread_mutex_t _mtx;  // 通过互斥锁保证队列安全
    pthread_cond_t _Empty; // 用它来表示bq 是否空的条件
    pthread_cond_t _Full;  //  用它来表示bq 是否满的条件
};

2. ConProd.cc

#include <iostream>
#include <pthread.h>
#include <ctime>
#include <unistd.h>

#include "BlockQueue.hpp"

void *productor(void *args)
{
    BlockQueue<int> *bq = (BlockQueue<int> *)args;
    while (true)
    {
        int a = rand() % 10 + 1;
        bq->push(a);
        std::cout << "productor生成的数据是" << a << std::endl;
    }
    return nullptr;
}

void *consumer(void *args)
{
    BlockQueue<int> *bq = (BlockQueue<int> *)args;
    while (true)
    {
        usleep(rand()%1000);
        int a;
        bq->pop(&a);
        std::cout << "consumer消费的数据是" << a << std::endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    // 随机数种子
    srand((uint64_t)time(nullptr) ^ getpid() ^ 0x202300);
    BlockQueue<int> *bq = new BlockQueue<int>();
    // 创建线程
    pthread_t c, p;
    pthread_create(&c, nullptr, consumer, bq);
    pthread_create(&p, nullptr, productor, bq);

    // 等待线程
    pthread_join(c, nullptr);
    pthread_join(p, nullptr);
    // 释放资源
    delete bq;

    return 0;
}

3. 结果展示

在这里插入图片描述

升级版测试&&设计与RAII风格的加锁方式

1. BlockQueue.hpp

#pragma once

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

#include "LockGuard.hpp"
#define gDefaultCap 5
template <class T>
class BlockQueue
{
    bool isQueueEmpty()
    {
        return _bq.size() == 0;
    }
    bool isQueueFull()
    {
        return _bq.size() == _capacity;
    }

public:
    BlockQueue(int capacity = gDefaultCap) : _capacity(capacity)
    {
        pthread_mutex_init(&_mtx, nullptr);
        pthread_cond_init(&_Empty, nullptr);
        pthread_cond_init(&_Full, nullptr);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_Empty);
        pthread_cond_destroy(&_Full);
    }
    void push(const T &in) // 生产者
    {
        // pthread_mutex_lock(&_mtx);
        LockGuard Lockguard(_mtx);
        while (isQueueFull())   //访问临界资源,100%确定,资源是就绪的!
            pthread_cond_wait(&_Full, &_mtx); // 检测临界资源条件
        // 数据满了; 等待消费者消费数据
        _bq.push(in); // 生产者放数据
        //if(_bq.size() >= _capacity/2) pthread_cond_signal(&_Empty); //数据积累到>=一半时再发送
        pthread_cond_signal(&_Empty); // 唤醒; 通知消费者,可以消费了
        //pthread_mutex_unlock(&_mtx);
    }// 出了函数后自动调用lockgrard 析构函数
    void pop(T *out) // 消费者
    {
        // pthread_mutex_lock(&_mtx);
        LockGuard Lockguard(_mtx);
        while (isQueueEmpty())
            pthread_cond_wait(&_Empty, &_mtx); // 检测临界资源条件
        // 数据为空; 等待生产者生产数据
        *out = _bq.front();
        _bq.pop();           // 消费者消费
        pthread_cond_signal(&_Full); // 唤醒; 通知生产者,可以生成了了
        // pthread_mutex_unlock(&_mtx);
    }

public:
    std::queue<T> _bq;     // 阻塞队列
    int _capacity;         // 容量上限
    pthread_mutex_t _mtx;  // 通过互斥锁保证队列安全
    pthread_cond_t _Empty; // 用它来表示bq 是否空的条件
    pthread_cond_t _Full;  //  用它来表示bq 是否满的条件
};

2. Task.hpp

#pragma once

#include <iostream>
#include <functional>

using func_t = std::function<int(int, int)>;

class Task
{
public:
    Task() {}                                                   // 便于获取任务
    Task(int x, int y, func_t func) : _x(x), _y(y), _func(func) // 制作任务
    {}

    int operator()() // 仿函数
    {
        return _func(_x, _y); // 函数调用
    }

public:
    int _x;
    int _y;
    func_t _func;
};

3. LockGuard.hpp(RAII风格的加锁方式)

#pragma once

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

class Mutex
{
public:
    Mutex(pthread_mutex_t& mtx):_mtx(mtx)
    {}
    void lock()
    {
        std::cout << "要进行加锁" << std::endl;
        pthread_mutex_lock(&_mtx);
    }
    void unlock()
    {
        std::cout << "要进行解锁" << std::endl;
        pthread_mutex_unlock(&_mtx);
    }
    ~Mutex()
    {}
public:
    pthread_mutex_t _mtx;
};

// RAII风格的加锁方式
class LockGuard
{
public:
    LockGuard(pthread_mutex_t& mtx):_mx(mtx)
    {
        _mx.lock();
    }
    ~LockGuard()
    {
        _mx.unlock();
    }

private:
    Mutex _mx;    
};

4. ConProd.cc

#include <iostream>
#include <pthread.h>
#include <ctime>
#include <unistd.h>

#include "BlockQueue.hpp"
#include "Task.hpp"

int myMul(int x, int y)
{
    return x * y;
}

void *productor(void *args)
{
    // BlockQueue<int> *bq = (BlockQueue<int> *)args;
    BlockQueue<Task> *bq = (BlockQueue<Task> *)args;
    while (true)
    {
        // int a = rand() % 10 + 1;
        // bq->push(a);
        // std::cout << "productor生成的数据是" << a << std::endl;

        // 制作任务
        int x = rand() % 10 + 1;
        usleep(rand() % 1000);
        int y = rand() % 5 + 1;
        std::cout << pthread_self() << ":发布任务..." << x << 'x' << y << "=" << '?' << std::endl; 
        Task t(x, y, myMul);
        bq->push(t);
        sleep(1);  
    }
    return nullptr;
}

void *consumer(void *args)
{
    // BlockQueue<int> *bq = (BlockQueue<int> *)args;
    BlockQueue<Task> *bq = (BlockQueue<Task> *)args;
    while (true)
    {
        // usleep(rand() % 1000);
        // int a;
        // bq->pop(&a);
        // std::cout << "consumer消费的数据是" << a << std::endl;
        // sleep(1);

        // 获取任务
        Task t;
        bq->pop(&t);

        // 完成任务
        std::cout << pthread_self() << ":执行任务..." << t._x << 'x' << t._y << "=" << t() << std::endl;
    }
    return nullptr;
}

#define CONSUMER_NUM 6
#define PRODUCTOR_NUM 3
int main()
{
    // 随机数种子
    srand((uint64_t)time(nullptr) ^ getpid() ^ 0x202300);
    // BlockQueue<int> *bq = new BlockQueue<int>();
    BlockQueue<Task> *bq = new BlockQueue<Task>();

    // 创建线程
    pthread_t c[CONSUMER_NUM], p[PRODUCTOR_NUM];
    for(int i=0; i<CONSUMER_NUM; ++i)
    {
        pthread_create(c+i, nullptr, consumer, bq);
    }
    for(int i=0; i<PRODUCTOR_NUM; ++i)
    {
        pthread_create(p+i, nullptr, productor, bq);
    }

    // 等待线程
    for(int i=0; i<CONSUMER_NUM; ++i)
    {
        pthread_join(c[i], nullptr);
    }
    for(int i=0; i<PRODUCTOR_NUM; ++i)
    {
        pthread_join(p[i], nullptr);
    }

    // 释放资源
    delete bq;

    return 0;
}

5. 结果展示:

在这里插入图片描述

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昨天;明天。今天。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值