【Linux】生产者与消费者模型以及代码实现

生产者消费者模型

  • 生产者消费者模型的简介
    • 产生的背景
    • 为什么要使用生产者消费者模型
  • 生产者消费者模型的优点
  • 生产者消费者模型的关系
  • C++ queue模拟实现阻塞队列的生产者消费者模型

生产者消费者模型的简介

产生的背景:生产者(线程)生产数据过快,消费者(线程)处理数据,生产者与消费者的速度并不均衡。导致生产或者处理的速度提不起来。

栗子: 一个线程从网卡上抓取数据流量,有一个线程进行数据分析,分析肯定是特别慢的,如果从网卡上抓取数据流量很慢的话,不代表流量产生的就慢,抓的慢说明丢包了,意味着你的数据抓取的不完整,不完整说明数据分析没有任何意义,生产数据的时候就一直抓取数据,又无法交给消费者,意味着你的数据只能丢掉,丢掉之后,就遗失数据。
为什么要使用生产者消费者模型??
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取数据,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
关于耦合性的解释
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。

生产者消费者模型图

在这里插入图片描述

生产者与消费者模型的优点:

  1. 支持闲忙不均
    当多个生产者线程生产数据的速度非常快时,消费者线程处理数据的速度慢于生产者生产数据的速度时,大量的数据可以先放置在阻塞队列或者缓冲区中,等待消费者处理数据;反之,生产者生产数据慢于消费者,则消费者等待阻塞队列中有数据在进行处理。
  2. 支持并发
    在线程安全的情况下,有多个消费者的时候,处理速度非常快,多个处理数据线程一起进行数据处理且互不影响。
  3. 解耦合
    封装就是一种解耦合,减小关联性,修改只修改一个,不会对另一个造成影响,生产者如果直接将数据交给消费者的话,之间的关联性很大,而现在有了队列,他们只与队列打交道,并不直接沟通,数据处理与数据生产直接没有直接关系。

生产者与消费者模型关系

生产者和生产者:具有互斥关系
消费者和消费者:具有互斥关系(一个消费者线程获取的节点数据,另一个不能获取)
生产者和消费者:具有互斥、同步关系(放完数据才能去取数据)

总结起来就是: 一个场所,两类角色(不同的两种线程),三种关系

C++ queue模拟实现阻塞队列的生产者消费者模型(手撕代码)

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

其实就是对互斥锁条件变量 的 应用
使用C++封装一个线程安全队列,也叫做阻塞队列,放满数据了就不能再放了

define MAX_QUE 10

class  BlockQueue
{
   //可以动态增长的队列,非安全的
   //而我们现在要实现的是阻塞的线程安全队列,当我们放满了数据,那么数据就不能往里面放了,
   //我们向其中添加的节点个数是有限的,所以我们要定义一个最大的节点限制capacity作为上限
   std::queue<int> _queue; 
   int _capacity; //最大的结点数量限制,达到容量表示队列满了
  
  //以上两个是为了实现队列而赋予的成员变量
  //下面的是基于线程安全的角度实现的变量
  
  //实现同步操作:没有数据的时候消费者要等待,数据满了的时候生产者要等待,
  //所以需要条件变量,条件变量使用两个,因为不同的角色要等待在不同的条件变量上,所以要使用两个不同的条件变量
pthread_cond_t  _cond_productor   //生产者等待队列
pthread_cond_t _cond_consumer     //消费者等待队列
pthread_mutex_t _mutex;           //定义互斥锁来保证线程安全,来实现互斥

public:
BlockQueue(int que_num = MAX_QUE):_capacity(que_num) //构造函数:确定队列的最大容量数,以及条件变量与互斥锁的初始化
{
    pthread_mutex_init(&_mutex, NULL);
    pthread_cond_init(&_cond_consumer,NULL);
    pthread_cond_init(&_cond_productor,NULL);
}
~BlockQueue() //析构函数:销毁互斥锁与条件变量
{
	pthread_mutex_destroy(&_mutex);
	pthread_cond_destroy(&_cond_consumer);
	pthread_cond_destroy(&_cond_productor);
}
bool QueuePush(int &data) //入队操作: 提供给生产者接口---数据入队
{
    pthread_mutex_lock(&_mutex); // queue是临界资源 需要加锁保护
    while(queue.size() ==capacity) //若队列中的数据已经满了,则把生产者添加到生产者等待队列中
    {
		 pthread_cond_wait(&_cond_productor,&_mutex);
    }
	_queue.push(data); //数据入队
	pthread_mutex_unlock(&_mutex); // 临界区临界资源queue保护完毕
	pthread_cond_signal(&_cond_consumer); //解锁完毕 唤醒消费者等待队列上的消费者,队列里面有数据,通知消费者可以读取了
	return true;
}
bool QueuePop(int &data)
{
 	pthread_mutex_lock(&_mutex); //加锁
	while (_queue.empty()) //如果队列为空
	{
 	   pthread_cond_wait(&_cond_consumer,&_mutex);//让消费者等待到消费者的等待队列
	}
	data = _queue.front();
	_queue.pop();  //数据出队
	pthread_mutex_unlock(&_mutex);//解锁
	pthread_cond_signal(&_cond_productor);// 唤醒生产者队列上的生产者,可以生产数据了
	return true;
};

注意事项:

  1. 构造函数的作用:确定队列的最大容量数,以及条件变量与互斥锁的初始化
  2. 析构函数的作用: 销毁互斥锁与条件变量
  3. 入队操作: 提供给生产者的接口—数据入队
  4. 出队操作: 提供接消费者的接口—数据出队
  5. 因为队列本身是一个临界资源,所以要对它的操作都要进行加锁操作(pthread_mutex_lock(&metex));
  6. 如果队列为空,消费者等待,接下来解锁(一个wait()接口完成这两步,wait()就是解锁+等待+加锁的步骤),完成出队,必须用while循环,支持并发的时候必须得判断。
  7. 如果队列满了才wait()让生产者等待,让生产者入队
  8. 当队列中有数据生产者唤醒消费者,告诉消费者有资源进行访问;当每个数据出队之后,都会唤醒生产者,告诉生产者可以生产数据了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值