【Linux】基于阻塞队列的生产消费模型

听到生产消费模型大家可能觉得高大尚。但是生产者和消费者的概念相信很多人都不陌生,还记得的那年高中。。。haha,不扯那么远了,高中的生物课上我们应该都画过生产者消费者之间的关系图,其实这里的生产消费模型和它差不多,看下面商场中的关系图你就能一目了然了~
在这里插入图片描述
由上面的图形可以看出,一个生产者消费者模型由三部分构成,生产资,消费者,交易场所。

生产消费模型遵循原则

简单记忆:321原则

  1. 三种关系
    生产者与生产者之间互斥关系,消费者与消费者之间互斥关系,生产者与消费者之间同步关系。
  2. 两种角色
    生产者,消费者
  3. 一个交易场所
    队列,链表,数组等任何一个可以作为缓冲区的结构

为什么需要生产消费模型

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

生产消费模型的优点

  • 解耦:有了此模型后购物时生产商品的厂家可以不是必须把商品送到消费者的手上,而是直接塞到超市中,这样使得厂家和消费者耦合度就大大减少了
  • 支持并发:如果没有超市那个交易场所,每一次购物你都需要一直等待商家将商品送到你手上,或者是商家每家每户询问谁家需要商品。有了次模型后,从线程的角度来说,他们是两个独立的并发主体,互不干扰的运行。
    支持忙闲不均:如果制造数据的速度时快时慢,缓冲区可以对其进行适当缓冲。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。

基于阻塞队列的生产消费模型

在多线程编程中阻塞队列是一种常用于实现生产者和消费者模型的数据结构。这里的阻塞队列与进程间通信中的管道作用相似,并且都自带同步互斥机制。其与普通队列的区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入元素;当队列满时,往队列里放元素的操作被阻塞,直到队列中有元素被取出。

模拟基于阻塞队列的生产消费模型

这里需要注意以下的代码因为没有规定队列的上限,所以为了避免生产者太快的情况我们让生产者生产一次就睡眠一秒,所以最后模型运行起来以后的现象是生产者生产一个数据,消费者就拿走一个数据。追加说明一点,因为这里的队列其实是共享资源,所以我们需要使用一把锁将他保护起来。
代码如下:

#include <iostream>
#include <queue>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>

using namespace std;

class BlockQueue
{
  private:
    queue<int> q;
    pthread_cond_t cond;
    pthread_mutex_t lock;
  private:
    void LockQueue()
    {
      pthread_mutex_lock(&lock);
    }
    void UnlockQueue()
    {
      pthread_mutex_unlock(&lock);
    }
    bool isEmpty()
    {
      return q.size()==0?true:false;
    }
    void WakeConsume()
    {
      pthread_cond_signal(&cond);
    }
    void ProductWait()
    {
      pthread_cond_wait(&cond,&lock);
    }
  public:
    BlockQueue()
    {
      pthread_mutex_init(&lock,NULL);
      pthread_cond_init(&cond,NULL);
    }
    void PushData(const int &data)  //生产者生产,并通知消费者
    {
      LockQueue();
      q.push(data);
      UnlockQueue();
      cout<<"product run done,data push sucess:"<<data<<endl;
      WakeConsume();
    }
    void PopData(int &data)  //消费者消费,无法消费时等待
    {
      LockQueue();
      while(isEmpty())  //用while可防止误唤醒同时等待条件满足
        ProductWait();
      data=q.front();
      q.pop();
      UnlockQueue();
      cout<<"consume run done,data pop sucess:"<<data<<endl;
    }
    ~BlockQueue()
    {
      pthread_cond_destroy(&cond);
      pthread_mutex_destroy(&lock);
    }

};
    void* product (void *arg) //生产行为
    {
      BlockQueue *bq=(BlockQueue*)arg;
      srand((unsigned long)time(NULL));
      while(1)
      {
        int data=rand()%100+1;
        bq->PushData(data);
        sleep(1);
      }
    }
    void* consume(void *arg)   //消费行为
    {
      BlockQueue *bq=(BlockQueue*)arg;
      while(1)
      {
        int d;
        bq->PopData(d);
      }
    }


int main()
{
  BlockQueue bq;
  pthread_t p,c;
  pthread_create(&p,NULL,product,(void*)&bq);
  pthread_create(&c,NULL,consume,(void*)&bq);
  pthread_join(p,NULL);
  pthread_join(c,NULL);
  return 0;
}

在这里插入图片描述
上面的代码你一定没问题,如果有兴趣的同学还可以进一步修改代码,因为这里我们也并没有维护生产者生产者,消费者消费者之间的关系,但是想维护这俩个关系也很简单,你只需多创建几个线程,然后再创建俩把锁,再这些生产者生产者和消费者消费者竞争队列资源时,加上锁就可以。
我就不在这里实现了,篇幅有点长~
下一篇会讲到基于环形队列的生产消费模型,有兴趣的记得戳链接哦~

猜你喜欢

转载自blog.csdn.net/ly_6699/article/details/98315588