生产者消费者模型概要和基本练习

生产者消费者模型

什么是生产者消费者模型?

完成某些操作的时候可能需要一些数据,这样的数据可能由专门的 线程/进程产生, 再由专门的线程/进程使用。

需要有一个交易场所。(存储数据的地方,可能是一个队列、栈或者其他数据结构)

生产者:负责产生数据,然后把数据放到交易场所中。
消费者:负责消费数据,从交易场所中获取走。

生产者消费者模型的要点。

  1. 一个交易场所(阻塞队列)。
  2. 两个角色(生产者消费者)。
  3. 三种关系:
    1. 消费者和消费者之间是互斥关系。两个消费者只有一个能拥有数据。
    2. 生产者和生产者之间是互斥关系。两个生产者只有一个可以往里面存数据,不能同时存。
    3. 生产者和消费者之间是同步互斥的关系。生产者和消费者必须按照一定的顺序执行。

阻塞队列的实现

第一种方式:

用队列和互斥量和条件变量来实现。

#pragma once
#include 
#include 
#include 

template <class T>
class BlockingQueue
{
public:
    BlockingQueue(int capacity)
        :_capicity(capacity)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_full, nullptr);
        pthread_cond_init(&_empty, nullptr);
    }

    virtual ~BlockingQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_full);
        pthread_cond_destroy(&_empty);
    }

    // 插入是生产者的动作
    void Push(const T data)
    {
        LockQueue();

        // 如果阻塞队列满了,就给消费者发信号
        // 让消费者拿数据
        while (IsFull())
        {
            NotifyConsume();

            std::cout << "BlockingQueue is full. Producer stop push." << std::endl;

            ProducerWait();
        }

        _qu.push(data);

        UnlockQueue();
    }

    // 删除是消费者的动作
    T Pop()
    {
        LockQueue();

        // 如果队列为空,给生产者发信号
        // 让生产者插入数据
        while (IsEmpty())
        {
            NotifyProducer();

            std::cout << "BlockingQueue is empty. Consume stop pop." << std::endl;

            ConsumeWait();
        }

        T ret = _qu.front();
        _qu.pop();

        UnlockQueue();

        return ret;
    }

private:
    void LockQueue()
    {
        pthread_mutex_lock(&_mutex);
    }

    void UnlockQueue()
    {
        pthread_mutex_unlock(&_mutex);
    }

    void ProducerWait()
    {
        pthread_cond_wait(&_full, _mutex);
    }

    void NotifyProducer()
    {
        pthread_cond_signal(&_full);
    }

    void ConsumeWait()
    {
        pthead_cond_wait(&_empty, _mutex);
    }

    void NotifyConsume()
    {
        pthread_cond_signal(&_empty);
    }

    bool IsEmpty()
    {
        return _qu.size() == 0 ? true : false;
    }

    bool IsFull()
    {
        return _qu.size() == _capicity ? true : false;
    }

private:
    std::queue<T> _qu;      // 用队列来实现一个阻塞队列
    pthread_mutex_t _mutex; // 锁来保证线程安全
    pthread_cond_t _full;   // 当队列满了,生产者需要等待消费者发送信号
    pthread_cond_t _empty;  // 当队列空了,消费者需要等待生产者发送信号
    int _capicity;
};

第二种方式:

用信号量和环形队列来实现。

#include 

template <class T>
class BlockingQueue
{
public:
    BlockingQueue(int capacity)
        :_head(0),
        _tail(0),
        _size(0),
        _capicity(capacity)
    {
        _qu.resize(capacity);
        sem_init(&_lock, 0, 1);
        sem_init(&_elem, 0, 0);
        sem_init(&_blank, 0, _capicity);
    }

    virtual ~BlockingQueue()
    {
        sem_destroy(&_lock);
        sem_destroy(&_elem);
        sem_destroy(&_blank);
    }

    void Push(const T& data)
    {
        // 每次插入元素前,申请一个空格资源
        // 如果没有空格资源,说明队列满了
        // 满了就不能继续插入,并且在 Push 中阻塞
        sem_wait(&_blank);

        sem_wait(&_lock);
        _qu[_tail++] = data;
        _tail %= _capicity;
        _size++;
        sem_post(&_lock);

        sem_post(&_elem);
    }

    void Pop(T* data)
    {
        // 每次出队列前要申请一个元素资源
        // 如果没有元素资源,队列为空
        // 不能出队列,要在Pop 中阻塞
        sem_wait(&_elem);

        sem_wait(&_lock);
        *data = _qu[_head++];
        _head %= _capicity;
        _size--;
        sem_post(&_lock);

        sem_post(&_blank);
    }

private:
    sem_t _lock;
    std::vector<T> _qu;
    int _head;
    int _tail;
    int _size;
    int _capicity;
    sem_t _elem;
    sem_t _blank;
};

简单的生产者和消费者举例

我的生产者和消费者是很简单的类型,基本上就是生产者插入数据,消费者读取数据。

#include "BlockingQueue.hpp"
#include 
#include 

void *Producer(void *arg)
{
    BlockingQueue<int> *p = (BlockingQueue<int> *)arg;
    srand(time(nullptr));

    while(true)
    {
        int data = rand() % 921;
        p->Push(data);

        std::cout << "Producer done. The data is --> " << data << std::endl;

        usleep(777777);
    }

    return nullptr;
}

void *Consume(void *arg)
{
    BlockingQueue<int> *p = (BlockingQueue<int> *)arg;

    while (true)
    {
        int data = p->Pop();
        
        std::cout << "Consume done. The data is --> " << data << std::endl;

        usleep(333333);
    }

    return nullptr;
}

int main(void)
{
    pthread_t tid1, tid2;
    BlockingQueue<int> bq(10);

    pthread_create(&tid1, nullptr, Producer, (void*)&bq);
    pthread_create(&tid2, nullptr, Consume, (void*)&bq);

    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);

    return 0;
}

你可能感兴趣的:(linux知识点)