Home

并发和并行

15 Dec 2012 by LelouchHe

推荐阅读

强烈推荐Rob Pike大牛的slide,这个slide解决了我很多的问题

我的理解

举个去饭店吃饭的例子,有这样一家饭店,总共只有一个员工S1,兼任服务员和厨师,那么去这家饭店吃饭肯定是如下的流程:A顾客来了,给A点餐,然后去厨房做饭,把菜端出来给A,然后接着处理顾客B,再然后处理顾客C.

这样的饭店不多,反正我是没有遇到过,这样做的一个很大的缺点就是每次都只能服务一位顾客,剩下的顾客都block在了吃饭的第一步上(即点餐),显然的,顾客的吃饭时间除了真正给他服务做饭的时间外,还有他等待前面顾客服务完毕的时间,这样做显然很难让顾客满意.

新员工来了

恰巧在这个时候,老板又招来了一个人S2,如何分配这个新员工,就有不同的做法了

做法一

S2执行和S1同样的工作,给顾客点餐,然后去厨房做饭,然后端回来给顾客.

这样做能不能提高服务的效率呢?

答案是不一定.

如果这家饭店的厨房只有一套厨具,那么还是每次只能有一个顾客用餐,但是这个时候等待的人群中有了分类,大多数顾客还是block在了吃饭的第一步点餐上,而只有一个顾客是block在了点餐完毕上菜的过程中.这样做显然稍微提高了点工作的效率,因为这样保证了至少厨房是比较忙的,顾客A的饭做好了,就得开始做B的饭,而无需等待B来点餐,这样虽然处理问题的时间没有变(点餐+做饭),但是等待的时间减少了(以前得等一个循环,现在至少有一个顾客只是等厨房空闲而已),使得饭店单位时间能够处理的问题变多了,提高了所谓的吞吐量

还有一种情况,这家饭店有两套餐具,那么提升就比较明显了,顾客A和顾客B基本可以同时吃饭了,因为S1和S2的服务并不冲突,二者可以同时进行,这样至少顾客B就无需等待顾客A的饭做好了,这个时候其实大多数人还是block了第一步,但是B却可以像第一个来吃饭的A一样,享受到无等待服务了.这样的好处和第一种情况类似,等待时间少了(没有顾客会等待厨房空间了,大家都是排队时间了)

可以看到,两种情况其实都可以增加单位时间处理顾客的数量,但处理单位问题的时间并没有增长,它减少的仅仅是等待时间而已.

做法二

新员工还可以有另一种用法,比如S1继续做他的服务员专门负责点餐,而S2来做厨师,专门负责做饭

现在的情况怎么样呢?厨房外的情况是A来了,S1给A点餐,把菜单传给S2;B来了,S1给B点餐,把菜单传给S2.厨房里就是S2接到菜单,做饭,送到门口传给S1,然后继续接菜单,继续做饭.

这样做能不能提高服务的效率呢?

答案是不一定

可以看到,现在所有顾客block的时间段分成了两段(而不像前面那些人,特定时间绝大多数等待的阶段都是一样的),一个是等待点餐(S1),一个是等待上餐(S2),如果处理能力不够,其实还是要等的,不过等归等,按照这样层次的划分,至少有一部分的顾客的等待时间减少了(因为他们直接等在了上餐上),而另一部分等待点餐的时间也少了(S1服务完刚才的人,不就可以直接来服务这些人了么),处理问题的时间仍然没有变,但是整体等待的时间减少了,和做法一一样,吞吐量上来了,等待时间减少了,但处理问题仍旧那样.

但情况是否真如这么美好呢?显然不是,因为我们不知道这两个服务段各自的特性,如果点餐远远慢于做饭,那么S1肯定会忙碌到要死,而S2肯定会闲到爆;如果点餐远远快于做饭,那么二者的情况恰好相反.这样的情况维持不了多久,劳累的一方肯定会要求涨工资的(要不就直接走人了),而且如果两个阶段的差距非常之大,以至于可以忽略掉耗时小的阶段,那么我们就又回到了开头的情况–一个服务员的悲催饭店

其实这是工程中经常用到的分层技术,只不过和所有分层一样,如果对的鉴定出现了问题,那么就徒增复杂度的玩具而已

又来了新员工

如果此时又来了新员工S3,我们怎么分配呢?除去上面的类似做法外,还有一些做法可以参考

做法三

点餐的时候容易出现这种情况,饭是好了,可是服务员S1在忙着点餐,无法顾及,厨师S2在忙着做饭,来不及送,这时虽然饭菜已经好了,但是顾客还是需要等待额外的时间才能用餐.

当然,做法之一是让顾客自己来取(很经典的callback调用),但如果我们有一个新员工S3,我们完全可以让S3负责送餐.这相当于再次的分层,以前是两层结构(S1–S2),现在变成了(S1–S2–S3)的结构,这样的好处我们上面已经提到了,此处略去

做法四

当然,还有很多类似分层的手段方法,也有一些类似做法一的方式,比如增加点餐的服务员,或者增加做饭的厨师等,好处和问题都在上面说过了,但是还有一种类似的做法我们没有提到

比如做饭的步骤比较复杂,有些步骤是必须串行的,菜总得洗了才能切,然后才能炒啊煮啊的,这是无法回避的,但是有些步骤则不然,比如准备菜和闷大米就没有什么相关性,我们平时的做法也是先闷上大米然后趁着时间来做其他事情(很像后台进程吧),这是可以放着不管的例子,也有一些是可以多个人一起做的,比如一桌饭菜肯定有很多个菜,这些菜都可以同时来做,之间除了可能会对厨具之类的用需求外,没有什么交集

所以我们的新员工S3就可以被安排来做这样的事情,也就是说,做那些可以一起做的事情.这种做法和上面说的所有做法都不一样,因为这种做法才能真真正正提高处理问题的速度,即顾客真正等待点菜上菜吃饭的时间才会真真正正的减少,因为以前多个事情顺序做,现在多个事情由多个人同时做,做完的时间也就是这些事情的单一完成的最长时间,自然比顺序完成要快的多.

做法回顾

其实如果你深入思考,做法四和做法一基本没什么本质区别,做法一要解决的很多顾客要吃饭,所以我们派来两个人解决;做法四是某个饭的要求很多,我们派来两个人同时来做,虽然面向的角度(一个是整体问题的解决–全部顾客的吃饭,一个是单个问题的解决–一个顾客的吃饭),但解决的途径都是一致的.

做法二和做法三也是同一种解法,即把一个过程拆分成不同的独立的过程,过程之间有接口进行通信,但过程和过程是各自独立的,点餐的S1肯定不走到厨房指挥S2,他只需要报个菜名,S2就会自动来做了,然后S3看到菜好了,就会自动的送出来.二者的区别也在于角度的不同,层层细化,逐个击破

诸多做法,本质上都在压榨可怜的服务员们,每种做法实际上都让服务员更加忙碌而没有空闲,当然,还有可怜的点餐器和厨具们,它们也越来越没有保养的时间啦(;().这可以看作是服务器处理性能的挖掘,服务器自然不会有怨言,CPU空闲的越少,内存使用的越满,其实是极大提高服务器处理吞吐的标志.

回到主题

现在我们再来看并发与并行,简单给个感觉的话,你可以把做法一和做法四看作并行,而做法二和做法三则是并发

并发的主题是独立同步,前者是分层的充分性条件,只有某项服务可以进行这样层次的独立拆分,我们才能使用并发的技术来保证各个层次的正常工作;后者则是并发服务能够正确运行的关键,没有了数据同步,各个独立模块间各行其事,显然是无法做到提供服务的.就好像做法二三中那样,点餐,做饭,送餐是独立的,而且他们通过各自的接口进行同步,比如点餐器,比如厨房窗户旁边的饭,这两点缺少任何一点,都不能完成任务(可以想象这三个搅成一锅粥的感觉,肯定是最烂的饭店了)

并行则更多和程序设计无关了,虽然看上去做法一四和二三都是增加人手了,但是我们可以这样来看,做法一其实相当于新开了家饭店,把原先一家的压力变成两家(所以我们看整体的话,两家自然比一家更好),做法四相当于同时请了很多厨师(肯定也好呗),但做法二三并没有类似的比喻.这样一说,其实大家可以理解了吧,为什么说做法一和做法四都能提高解决问题的速度(虽然一个是整体而言,一个是单个而言),但做法二三仅能减少等待时间.

看到了么?并行更多的是硬件机器层面上的思路,一个CPU跑不动了,咱们分成两份放两个CPU上;一个机器顺序查询慢了,咱们把请求发到不同机器同时请求去.你看,这就是并行,它的主题是同时,即可以真正同时的处理问题

混淆

很多人无法分清楚二者的区别,因为我们在使用的时候,二者区别被很多东西掩盖了,比如说我们用多线程技术,这个算什么?这需要看我们是怎么处理的,如果我们只是开了类似线程池的东东,有任务请求就随便拿个线程来做,大家做的都是一样的事情,那么在单CPU上就叫伪并行,多CPU上就叫部分并行,有和线程数一样的CPU且线程平均到每个CPU上,就叫全部并行(这些名词都是我杜撰的);但是如果我们将线程做了分类,比如派发任务的主线程,处理连接的IO线程,真正处理任务的工作线程,定期检测的监控线程,那么除了上面的”并行”外,就有了并发的存在.

还有一个就是同步问题了,因为并行和并发都涉及到这个内容,所以很多时候我们就混淆了概念.同步其实是并发的关键词,没有同步并发就是无用的,有了同步并发才能协调独立模块间的合作来共同的完成任务的处理.但对于并行而言,其实同步是两个因素造成的,一个是cpu问题,区分伪并行和真并行,第二个race问题,而race问题的核心是,并行的程序并不是完全的可以同时进行的(试想,如果真的可以,谁还关心同步啊),也就是说,可以并行的程序间需要合作(大的race了,比如消息队列)或者更根本的,有些任务压根就不能划分成完全独立的步骤,因为他们需要从整体上分解,或者需要最后从整体上合并.

简单的例子,计数器,每次访问都要加一做记录,如果采用多线程(或进程),就需要同步了,因为多个(或伪)同时操作,需要在计数器这个资源上做合并工作,这明显表明计数器任务是无法真正彻底并行的(换句话说,如果每个线程一个计数器,这个问题如何?)

看到没?这说明同步不是并行的特征,仅仅是我们在处理问题上分而治之之前或之后的扫尾,如果不需要这种扫尾,那么OK,肯定没有同步

比如快排,除了精妙的分治法外,这个就是无需同步的典型,我们把序列分成两部分之前我们只能单个处理,一旦分化,我们就无需管这两部分是怎么排了,因为他们不影响最后结果,我们无需最后的收尾工作

除此之外,更让人混淆的地方在于,并发的设计往往是并行的,所以人们就误以为二者等同了.所以我在这说,并发的设计也有不是并行的啊,比如利用协程,比如说callback(这个用的够广了吧).

优缺点

二者都是提高服务性能的手段,并发着重与提高吞吐量,让整个系统的原有性能充分挖掘(一直保持忙碌状态,不过前提是设计的好的并发),并行则是真正提高处理单个问题的处理速度(当然,伪并发还有切换的开销之类).可以看到,这是两个不同的方面

联系

并发的设计思想随处可见,它的一大好处在于,可以很轻松的利用并行来提升性能,整体而言或者单个而言都是如此

(待续,具体的例子还在想)