前面讲了线程之前的各种关系,以及它的概念之类的东西,下面就介绍一种特殊的线程间的关系,即生产者消费者模型,它是将实际开发过程中的数据产生模块和处理模块形象的用生产者消费者来表示。
但是仅有生产者和消费者还是不够的,还需要一块缓冲区来作为媒介,举 一个寄信的例子(虽说这年头寄信已经不时兴,但这个例 子还是 比较贴切的)。假设你要寄 一封平信,大致过程如下:1、你把信写好——相当于 生产者制造数据2、你把信放 入邮筒——相当于 生产者把数据放入缓冲区3、邮递员把信从邮筒取出——相当于消费者把数据取出缓冲区4、邮递员把信拿去邮局做相应的处理——相当于消费者处理数据
优点
那么这个缓冲区有什么用呢?为什么不让生产者直接调用消费者的某个函数,直接把数据传递过去?
大概有如下 一些好处。
◇解耦
假设生产者和消费者分别是两个类。如果让 生产者直接调用消费者的某个 方法,那么 生产者对于消费者就会产 生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到 生产者。 而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。
接着上述的例 子,如果不使 用邮筒(也就是缓冲区),你必须得把信直接交给邮递员。有同学会说,直接给邮递员不是挺简单的嘛?其实不简单,你必须得认识谁是邮递员,才能把信给他(光凭身上穿的制服,万一有 人假冒,就惨了)。这就产 生和你和邮递员之间的依赖(相当于生产者和消费者的强耦合)。万一哪天邮递员换 人了,你还要重新认识 一下(相当于消费者变化导致修改 生产者代码)。 而邮筒相对来说 比较固定,你依赖它的成本就 比较低(相当于和缓冲区之间的弱耦合)。
◇ 支持并发(concurrency)
生产者直接调 用消费者的某个 方法,还有另一个弊端。由于函数调用是同步的(或者叫阻塞的),在消费者的 方法没有返回之前, 生产者只好 一直等在那边。万 一消费者处理数据很慢, 生产者就会 白 白糟蹋 大好时光。
使 用了 生产者/消费者模式之后, 生产者和消费者可以是两个独 立的并发主体(常见并发类型有进程和线程两种,后 面的帖 子会讲两种并发类型下的应 用)。 生产者把制造出来的数据往缓冲区一丢,就可以再去 生产下 一个数据。基本上不 用依赖消费者的处理速度。
其实当初这个模式,主要就是 用来处理并发问题的。
从寄信的例 子来看。如果没有邮筒,你得拿着信傻站在路口等邮递员过来收(相当于生产者阻塞);又或者邮递员得挨家挨户问,谁要寄信(相当于消费者轮询)。不管是哪种 方法,都挺 土的。
◇ 支持忙闲不均
缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。
为了充分复用,我们再拿寄信的例 子来说事。假设邮递员 一次只能带走1000封信。万 一某次碰上情人节(也可能是圣诞节)送贺卡,需要寄出去的信超过1000封,这时候邮筒这个缓冲区就派上 用场了。邮递员把来不及带走的信暂存在邮筒中,等下次过来时再拿走。
其实整个过程都遵循三二一原则:
即三种关系(生产者与生产者之间互斥关系,消费者与消费者之间是互斥关系,生产者与消费者之间是同步互斥关系)
两个对象(生产者与消费者)
一个交易场所(缓冲区)
下面就以实例的代码来说明问题。
单链表模拟场景
:
/*************************************************************************
> File Name: mycp.c
> Author: Maple
> Mail: rendengbin_123@163.com
> Created Time: 2017年06月25日 星期日 14时22分13秒
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#pragma warning (disable:4996)
typedef int DataType;
typedef struct ListNode
{
DataType data;
struct ListNode* next;
}ListNode;
ListNode*