多线程实现生产者与消费者模式

生产者-消费者模式的简介:

在实际的软件开发过程中,我们将产生数据的模块称为生产者,处理数据的模块成为消费者。但仅有这两者还不够成为一个生产者-消费者模式,还需要有一个缓冲区(一段内存区域)作为中介,生产者产生的数据放入缓冲区,消费者从缓冲区读取数据并处理。(注:上述所说的模块是广义的,可以是类,函数,线程,进程等)

我们可以将这二者之间的关系图表示出来:

这里写图片描述

总结:我们用3-2-1的方法来简单描述一个生产者-消费者模式,3代表有三种关系:生产者-生产者,消费者-消费者,生产者-消费者;2代表两个角色:生产者,消费者;1代表一个交易场所:缓冲区

生产者-消费者模式之间的三种关系:

模拟实现生产者-消费者模式之前,我们需要先捋清除这之间的关系:

  • 生产者-生产者:很明显,这两者之间必定是一种竞争关系,也就是说一个生产者往缓冲区放数据时另一个生产者就不能去访问这块空间
  • 消费者-消费者:同样,两个消费者之间也是竞争的关系,这就好比两个人同时看中一件商品时,他们之间就是一种竞争的关系
  • 生产者-消费者:生产者与消费者之间其实是一种同步与互斥的关系,假设只有一个生产者一个消费者时,只有生产者放入数据后消费者才能读取,消费者拿到数据后生产者才去生产,这就是一种同步;但当生产者生产数据的时候消费者就不能从缓冲区拿数据,或者消费者读数据的时候生产者就不能往缓冲区里写数据,否则很可能会导致两者都存/取数据失败,产生二义性问题。

生产者-单消费者的实现:

  • 方案一:使用互斥锁+条件变量
    我们现在模拟一个场景,生产者将生产的数据放入交易场所,消费者从中读取。这个场所我们就用单链表来模拟(它还可以是任意一个数据结构,只要能够存储数据),生产者放入数据时进行头插,消费者消费数据时进行头删(这里我采用的是头插头删的方式,当然也可以用尾插、尾删的方式实现):

这里写图片描述

既然已经确定了生产场所,其次我们要考虑到如何才能让这二者时间实现同步与互斥。多线程编程中提到了“条件变量(Condition Variable)”,在此,我们再次简单回顾一下这几个函数:(文章链接:http://blog.csdn.net/qq_33951180/article/details/72801651

//初始化一个条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 
int pthread_cond_init(pthread_cond_t *restrict cond,
                      const pthread_condattr_t *restrict attr);  

//销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);  

//唤醒等待的线程 
int pthread_cond_signal(pthread_cond_t *cond); //唤醒一个 
int pthread_cond_broadcast(pthread_cond_t *cond);  //唤醒所有

//阻塞等待  
int pthread_cond_wait(pthread_cond_t *restrict cond,
                      pthread_mutex_t *restrict mutex);
//设定超时等待
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                       pthread_mutex_t *restrict mutex,  
                       const struct timespec *restrict abstime);  

我们可以利用条件变量中的相关函数来模拟实现,当生产者线程生产数据时消费者线程就wait,当消费者被唤醒后,此时链表已经有数据(消费者等待成功),拿走结点;消费者读取数据的过程中生产者在wait,等到消费者读取数据完之后被唤醒,然后继续往缓冲区写入数据(循环………),等待与被唤醒的过程就可以利用上述的pthread_cond_wait和pthread_cond_signal函数实现。

代码以托管到github:
https://github.com/lybb/Linux/tree/master/Product_Consumer/product_consumer_2_19

  • 方案二:使用信号量实现
    我们将方案一中的链表这次该为环形队列,环形队列使用数组模拟实现(循环链表也可以),如图:

这里写图片描述

我们将数组想象成上图中的环形队列,生产者和消费者都从0号下标的位置开始走,一开始,环形队列中有N个空格子和0个数据,因此,在生产者没有生产数据之前,消费者是不能进行消费的;
当生产者生产的数据占满整个环形buf时,假设消费者还在0号位置,生产者和消费者就会再次在0号位置相遇,此时有0个block资源和N个data资源,因此生产不能继续放入数据;
当消费者和生产者都在执行时,一旦消费者追上了生产者,消费者就没有资源可供使用了。

综上:使用环形buf的情况下,生产者和消费者必须满足:
①一开始,必须保证生产者先运行;
②生产者不能将消费者套圈(参考上述第二点);
③消费者不能追上生产者(参考第三点)

对于生产者来说:只关心空格子(block)资源
对于消费者来说:只关心数据(data)资源

因此,我们在用信号量实现时,需要两个信号量来分别表示block和data资源。

代码上传至github:
https://github.com/lybb/Linux/tree/master/Product_Consumer/Ring

  • 两种实现方案的比较:
    互斥锁+条件变量实现的是有锁同步,而信号量实现的是无锁同步
发布了83 篇原创文章 · 获赞 84 · 访问量 25万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览