[2]:队列缓冲区 shell
通过前面两个帖子的铺垫,今天终于开始聊一些具体的编程技术了。因为不一样的缓冲区类型、不一样的并发场景对于具体的技术实现有较大的影响。为了深刻浅出、便 于大伙儿理解,我们先来介绍最传统、最多见的方式。也就是单个生产者对应单个消费者,当中用队列(FIFO)做缓冲。 编程
关于并发的场景,在以前的帖子“进程还线程?是一个问题!”中,已经专门论述了进程和线程各自的优缺点,二者皆不可偏废。因此,后面对各类缓冲区类型的介绍都会同时说起进程方式和线程方式。 安全
★线程方式 性能优化
先来讲一下并发线程中使用队列的例子,以及相关的优缺点。 数据结构
◇内存分配的性能 并发
在线程方式下,生产者和消费者各自是一个线程。生产者把数据写入队列头(如下简称push),消费者从队列尾部读出数据(如下简称pop)。当队列为空,消费者就稍息(稍事休息);当队列满(达到最大长度),生产者就稍息。整个流程并不复杂。 编程语言
那么,上述过程会有什么问题捏?一个主要的问题是关于内存分配的性能开销。对于常见的队列实现:在每次push时,可能涉及到堆内存的分配;在每次pop 时,可能涉及堆内存的释放。假如生产者和消费者都很勤快,频繁地push、pop,那内存分配的开销就很可观了。对于内存分配的开销,用Java的同窗可 以参见前几天的帖子“Java性能优化[1]”;对于用C/C++的同窗,想必对OS底层机制会更清楚,应该知道分配堆内存(new或malloc)会有 加锁的开销和用户态/核心态切换的开销。 性能
那该怎么办捏?请听下文分解,关于“生产者/消费者模式[3]:环形缓冲区”。 优化
◇同步和互斥的性能 操作系统
另外,因为两个线程共用一个队列,天然就会涉及到线程间诸如同步啊、互斥啊、死锁啊等等劳心费神的事情。好在"操做系统"这门课程对此有详细介绍,学过的 同窗应该还有点印象吧?对于没学过这门课的同窗,也没必要难过,网上相关的介绍挺多的(好比"这里"),大伙本身去瞅一瞅。关于这方面的细节,咱今天就很少 啰嗦了。
这会儿要细谈的是,同步和互斥的性能开销。在不少场合中,诸如信号量、互斥量等玩意儿的使用也是有不小的开销的(某些状况下,也可能致使用户态/核心态切换)。若是像刚才所说,生产者和消费者都很勤快,那这些开销也不容小觑啊。
这又该咋办捏?请听下文的下文分解,关于“生产者/消费者模式[4]:双缓冲区”。
◇适用于队列的场合
刚才尽批判了队列的缺点,难道队列方式就一无可取?非也。因为队列是很常见的数据结构,大部分编程语言都内置了队列的支持(具体介绍见"这里"),有些语 言甚至提供了线程安全的队列(好比JDK 1.5引入的ArrayBlockingQueue)。所以,开发人员能够捡现成,避免了从新发明轮子。
因此,假如你的数据流量不是很大,采用队列缓冲区的好处仍是很明显的:逻辑清晰、代码简单、维护方便。比较符合KISS原则。
★进程方式
说完了线程的方式,再来介绍基于进程的并发。
跨进程的生产者/消费者模式,很是依赖于具体的进程间通信(IPC)方式。而IPC的种类名目繁多,不便于挨个列举(毕竟口水有限)。所以我们挑选几种跨平台、且编程语言支持较多的IPC方式来讲事儿。
◇匿名管道
感受管道是最像队列的IPC类型。生产者进程在管道的写端放入数据;消费者进程在管道的读端取出数据。整个的效果和线程中使用队列很是相似,区别在于使用管道就无需操心线程安全、内存分配等杂事(操做系统暗中都帮你搞定了)。
管道又分命名管道和匿名管道两种,今天主要聊匿名管道。由于命名管道在不一样的操做系统下差别较大(好比Win32和POSIX,在命名管道的API接口和 功能实现上都有较大差别;有些平台不支持命名管道,好比Windows CE)。除了操做系统的问题,对于有些编程语言(好比Java)来讲,命名管道是没法使用的。因此我通常不推荐使用这玩意儿。
其实匿名管道在不一样平台上的API接口,也是有差别的(好比Win32的CreatePipe和POSIX的pipe,用法就很不同)。可是咱们能够仅 使用标准输入和标准输出(如下简称stdio)来进行数据的流入流出。而后利用shell的管道符把生产者进程和消费者进程关联起来(没据说过这种手法的 同窗,能够看"这里")。实际上,不少操做系统(尤为是POSIX风格的)自带的命令都充分利用了这个特性来实现数据的传输(好比more、grep 等)。
这么干有几个好处:
一、基本上全部操做系统都支持在shell方式下使用管道符。所以很容易实现跨平台。
二、大部分编程语言都可以操做stdio,所以跨编程语言也就容易实现。
三、刚才已经提到,管道方式省却了线程安全方面的杂事。有利于下降开发、调试成本。
固然,这种方式也有自身的缺点:
一、生产者进程和消费者进程必须得在同一台主机上,没法跨机器通信。这个缺点比较明显。
二、在一对一的状况下,这种方式挺合用。但若是要扩展到一对多或者多对一,那就有点棘手了。因此这种方式的扩展性要打个折扣。假现在后要考虑相似的扩展,这个缺点就比较明显。
三、因为管道是shell建立的,对于两边的进程不可见(程序看到的只是stdio)。在某些状况下,致使程序不便于对管道进行操纵(好比调整管道缓冲区尺寸)。这个缺点不太明显。
四、最后,这种方式只能单向传数据。好在大多数状况下,消费者进程不须要传数据给生产者进程。万一你确实须要信息反馈(从消费者到生产者),那就费劲了。可能得考虑换种IPC方式。