自学内容网 自学内容网

【线程】基于环形队列的生产者消费者模型

1 环形队列

环形队列采用数组来模拟,用取模运算来模拟环状特性。
在这里插入图片描述
1.如何判断环形队列为空或者为满?

  • 当环形队列为时,头和尾都指向同一个位置。
  • 当环形队列为时,头和尾也都指向同一个位置。

因此, 可以通过加计数器或者标记位来判满或者空,也可以预留一个空的位置,作为满的状态

1.1 生产者消费者中的环形队列

2.生产者和消费者什么时候会访问同一个位置?

1.环形队列为空的时候,生产者和消费者会访问同一个位置。
在这里插入图片描述
环形队列中,如果队列为空,那么队首和队尾的指针是指向同一个位置的。所以,生产者和消费者也会指向同一个位置。

2.环形队列为满的时候,生产者和消费者会访问同一个位置。
在这里插入图片描述
环形队列中,如果队列为满,那么队首和队尾的指针是指向同一个位置的。所以,生产者和消费者也会指向同一个位置。

其他任何时候,生产者和消费者访问的都是不同的区域。
在这里插入图片描述

3.环形队列中的数据需要注意什么?

1.消费者不能超过生产者。
在这里插入图片描述
也就是: 不能没有数据了还继续消费

2.生产者不能将消费者套圈。
在这里插入图片描述
也就是: 不能没有空间了还继续生产

2 设计信号量

生产者最在意的是空闲的空间个数
消费者最在意的是数据的个数

所以生产者每次在访问临界资源之前,需要先申请空间资源的信号量,申请成功就可以进行生产,否则就阻塞等待。

消费者在访问临界资源之前,需要申请数据资源的信号量,申请成功就可以消费数据,否则就阻塞等待。

空间资源信号量的申请由生产者进行,归还(V操作)由消费者进行,表示生产者可以生产数据。

数据资源信号量的申请有消费者进行,归还(V操作)由生产者进行,表示消费者可以进行消费.

4.空间资源信号如何设计?

最开始,生产者没有生产,所以空间资源是队列的大小(满的)

5.数据资源信号如何设计?

最开始,生产者没有生产,所以数据资源为0(空的)

2.1 生产者伪代码

productor_sem = 环形队列大小;

P(productor_sem);//申请空间资源信号量
//申请成功,继续向下运行。
//申请失败,阻塞在申请处。

.......//从事生产活动——把数据放入队列中

V(comsumer_sem);//归还数据资源信号量

2.2 消费者伪代码

comsumer_sem = 0;

P(comsumer_sem);//申请数据资源信号量
//申请成功,继续向下运行。
//申请失败,阻塞在申请处。

.......//从事消费活动——从队列中消费数据

V(proudctor_sem);//归还空间资源信号量

在环形队列中,大部分情况下单生产和单消费是可以并发执行的,只有在满或者空的时候,才会有同步和互斥问题,同步和互斥是通过信号量来实现的。

3 代码

3.1 RingQueue.h

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

//为什么基于阻塞队列的生产者消费者模型只需要一个锁 , 而基于环形队列的生产者消费者模型需要两个锁?
//1.因为阻塞队列是对同一个队列的同一个位置进行操作,队列是共享资源,需要对整个队列加锁
//2.阻塞队列中,生产者和消费者访问的不是同一个下标,所以两者是互不干扰的,只需要用条件变量来唤醒阻塞
//但是生产者对生产者而言,是需要加锁的。消费者对消费者而言,是需要加锁的。

template <class T>
class RingQueue
{
    static const int defaultnum = 5;
public:
    //p操作,申请信号量,信号量--
    void p(sem_t& sem)
    {
        sem_wait(&sem);
    }

    //v操作,释放信号量,信号量++
    void v(sem_t& sem)
    {
        sem_post(&sem);
    }

    void Lock(pthread_mutex_t& mutex)
    {
        pthread_mutex_lock(&mutex);
    }

    void UnLock(pthread_mutex_t& mutex)
    {
        pthread_mutex_unlock(&mutex);
    }

public:
    RingQueue(int cap = defaultnum)
    :_ringqueue(cap)
    ,_cap(cap)
    ,_c_step(0)
    ,_p_step(0)
    {
        //初始化信号量,给消费者初始的信号量为0,给生产者初始的信号量为cap(满)
        //因为最开始的时候没有商品,无法消费,只能生产
        sem_init(&_c_data_sem, 0, 0);
        sem_init(&_p_space_sem, 0, cap);

        pthread_mutex_init(&_c_mutex, nullptr);
        pthread_mutex_init(&_p_mutex, nullptr);
    }

    //生产商品
    void push(const T &in)
    {   
        //1.申请信号量
        p(_p_space_sem);
        //2.消费者加锁
        Lock(_p_mutex);

        //3.进行生产
        _ringqueue[_p_step] = in;
        _p_step++;
        _p_step %= _cap;  //保证下标一直都在环形队列里面

        //4.解锁
        UnLock(_p_mutex);
        //5.释放信号量
        v(_c_data_sem);
    }

    void pop(T* out)
    {
        //1.申请信号量
        p(_c_data_sem);
        //2.申请锁
        Lock(_c_mutex);

        //3.消费数据
        *out = _ringqueue[_c_step];
        ++_c_step;
        _c_step %= _cap;

        //4.解锁
        UnLock(_c_mutex);
        //5.生产者信号量++
        v(_p_space_sem);  //消费完数据之后,生产者的信号量++
    }
private:
    std::vector<T> _ringqueue;
    int _cap;     //capacity
    int _c_step;  //消费者在环形队列中的下标
    int _p_step;  //生产者在环形队列中的下标

    sem_t _c_data_sem;  //消费者关注的数据资源
    sem_t _p_space_sem; //生产者关注的消费资源

    pthread_mutex_t _c_mutex;   //消费者锁
    pthread_mutex_t _p_mutex;   //生产者锁
};

3.1.1 生产商品

//生产商品
    void push(const T &in)
    {   
        //1.申请信号量
        p(_p_space_sem);
        //2.消费者加锁
        Lock(_p_mutex);

        //3.进行生产
        _ringqueue[_p_step] = in;
        _p_step++;
        _p_step %= _cap;  //保证下标一直都在环形队列里面

        //4.解锁
        UnLock(_p_mutex);
        //5.释放信号量
        v(_c_data_sem);
    }

6.为什么要按照 申请信号量 → 加锁 → 解锁 → 释放信号量 的顺序来做?

如果先加锁再申请信号量,可能会出现以下这种情况:
加锁之后,发现没有空闲空间了,于是阻塞。而此时该线程还持有锁,就会导致死锁。.
如果先释放信号量再解锁,可能会出现以下这种情况:
释放信号量之后,消费者得知有可以消费的资源,于是开始消费。但是此时并没有解锁,因此生产者可能并没有完全完成生产,但是消费者已经开始消费。就会导致生产消费数据不一致的问题。.

3.1.2 消费商品

void pop(T* out)
    {
        //1.申请信号量
        p(_c_data_sem);
        //2.申请锁
        Lock(_c_mutex);

        //3.消费数据
        *out = _ringqueue[_c_step];
        ++_c_step;
        _c_step %= _cap;

        //4.解锁
        UnLock(_c_mutex);
        //5.生产者信号量++
        v(_p_space_sem);  //消费完数据之后,生产者的信号量++
    }

在这里插入图片描述


原文地址:https://blog.csdn.net/qq_64076540/article/details/145429998

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!