【Linux】基于环形队列的生产消费模型

在上篇博客基于阻塞队列的生产者消费者模型中我介绍了什么是生产者消费者模型,还没了解的可以戳链接查看。本篇博客也是实现一个生产者消费者模型,只是这篇博客我将基于循环队列实现生产消费模型。

POSIX 信号量

这里要注意了,我们这次使用的是基于POSIX的信号量,千万不要误认为是SystemV的信号量,虽然两个东西不一样,但是他们的作用相同,他们都是用于保证同步的操作,达到无冲突访问共享资源的目的,但是POSIX信号量可以用于线程同步。

信号量本质上是一个记录临界资源数目的计数器,它有两个很重要的操作,一个是加操作,也被称为v操作,此操作会让信号量加一,另一个操作为减操作,也被称为p操作,此操作会让信号量值减一,接下来我们看看信号量的操作函数。

这里需要特别注意一点:虽然它只是是一个计数器,但是不能用全局变量代替,因为信号量的PV操作是具有原子性的,而我们自己定义的全局变量无法保证原子性。

信号量的相关操作
  1. 初始化信号量

#include
int sem_init(sem_t *sem,int pshared,unsigned int value);
sem:信号量
//pshared:0表示线程间共享,非零表示进程间共享
//value:信号量初始值

  1. 摧毁信号量

int sem_destroy(sem_t *sem);

  1. 等待信号量

int sem_wait(sem_t *sem);
功能:等到信号量时会将信号量的值减一,对应P操作

  1. 发布信号量

int sem_post(sem_t *sem);
功能:资源使用结束后,归还资源将信号量的值加一,对应V操作

基于循环队列的生产消费模型

上节中,我们实现了基于阻塞队列的,其空间大小可以进行动态分配。
现在我们将基于固定大小的环形队列再次实现这个模型。生产者每向队列中生产一批数据,消费者就可以从队列中读取到一批数据,所以此时循环队列就变成了交易场所,p表示生产者,c表示消费者,当生产者向队列中push后消费者就可以将数据拿走。

这里有几个原则需要遵循:

  1. 消费行为不能先于生产行为
  2. 生产行为不能超过消费行为一圈
  3. 虽然空 / 满的状态不好确定,但它俩一定是在同一点
  4. 队列为空时只能有生产行为,为满时只能有消费行为

【Linux】基于环形队列的生产消费模型_第1张图片
这时最大的问题是:环形结构的其实状态和结束状态都是一样的,不好判断为空还是未满,所以我们通过加计数器或者标志位来判断,也可以通过预留一个空的位置作为满的状态。

我们现在有信号量这个计数器,所以我们用数组moni 环形队列,用模运算模拟环形的特性。就能很简单的实现多线程间的同步过程。
生产者(P)的操作包括取空位增数据

P(sem_space)空位个数- -
V(sem_data)数据个数+ +

消费者(C)的操作包括取数据增空位

P ( sem_data ) 数据个数- -
V(sem_space) 空位个数+ +

话不多说,看下面的代码就能明白啦:

#include  
#include 
#include 
#include 
#include 
#include 
#include 

#define NUM 16
using namespace std;

class RingQueue{
private:
  vector q;  //模拟环形对列数组
  int cap;  //资源的数量
  sem_t data_sem;  //数据
  sem_t space_sem;  //空位
  int consume_step;  //消费者所在位置
  int product_step;  //生产者所在位置
public:
  RingQueue(int _cap=NUM):q(_cap),cap(_cap)
  {
    sem_init(&data_sem,0,0);
    sem_init(&space_sem,0,cap);
    consume_step=0;
    product_step=0;
  }
  void PutData(const int &data)  //生产者行为
  {
    sem_wait(&space_sem);  //等待信号对应减一
    q[product_step]=data;
    product_step++;
    product_step%=cap;  //保证不越界
    sem_post(&data_sem);  //发布信号对应加一
  }
  void GetData(int &data)   //消费者行为
  {
    sem_wait(&data_sem);  //等待数据
    data=q[consume_step];
    consume_step++;
    consume_step%=cap;
    sem_post(&space_sem);  //发布空位
  }
  ~RingQueue()
  {
    sem_destroy(&data_sem);
    sem_destroy(&space_sem);
  }
};

void *consume(void *arg)
{
  RingQueue *rq=(RingQueue*)arg;
  int data;
  while(1)
  {
    rq->GetData(data);
    cout<<"Consume data done:"<PutData(data);
    cout<<"Product data done:"<

【Linux】基于环形队列的生产消费模型_第2张图片

相信这里的代码也很容易理解。
我们现在实现了基于阻塞队列和基于信号量的生产者消费者模型,对于他们的实现其实都不是很难,重点是读者们需要理解问题解决的办法和理解模型本身的作用和意义。

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