巴别塔上的雇工


异步是正道:Asynchronous Is The Right Way
2月 20, 2008, 11:22 上午
Filed under: 技术体会

今天ASP.NET Advanced的培训终于结束了,两天之内被灌输了这么多东西,真是够累的。Jeff说如果我们只能从培训中学到一样东西的话,那么他希望是ASP.NET的Asynchronous Programming Model,专家这么说,可见其重要性,但是在课余聊天的时候,似乎有的同事还是不大理解。

晚饭去麦当劳买麦辣鸡翅吃,嘿,他们的服务流程就是Async的,下次你去麦当劳吃饭可以注意一下这种Async服务,我相信中国所有的麦当劳点在高峰期都是这样的服务方式(美国大多都是开车的Drive-thru,有点不一样)。

收款台永远是有限的,顾客多的时候就得排队,而且实际上每个柜台有两个队,一个是排队订餐的(叫A队吧),另外一个是等着拿食物的(B队),你首先在A队,轮到你的时候,收款员问你要什么吃的,下了单,收了钱,然后就请你去B队等着拿食品,然后他/她就可以服务下一位顾客了,准备食物需要一些时间,当你订的食物准备好了的时候(你的食物完全有可能比排在你前面的人早做好,所以B队实际上也不是先来先走),服务员(可能是刚才的收款员也可能是其他服务员)会把食物拿到柜台,你拿走,这个过程就结束了。这个过程是不是很合理,这其实就是异步(Asynchronous)方式。

如果不是Asynchronous方式,换作Synchronous方式,那会是怎样?那就只有一个队,轮到你的时候,接待的服务员全程提供服务,服务员下单收钱,然后就去拿食品,结果这个食品可能还没做好(比如我最喜欢的麦辣鸡翅,似乎每次都要花点时间),这位服务员就等着,你也等着,后面排队的顾客也等着,等到鸡翅做好之后,服务员把食物给你,然后才能服务下一个顾客。在非高峰期的时候,麦当劳也这么做,但是如果人一多,这样就弄不过来了。

假如下单收钱平均需要5秒,准备食物平均需要10秒,这种同步(Sync)方式一分钟之内只能服务60/(5+10)=4位顾客;用上面讲的异步(Async)方式,一分钟能够服务60/5=12位顾客,4:12=1:3,差异太明显了。你可能要说,麦当劳商家是爽了,但是从顾客的角度来看,我从点餐到拿到餐不还是需要15秒吗?没错,但是从你走进麦当劳开始排队到拿到食物的时间变短了,你不能指望每次进麦当劳都不用排队吧:) 假如一开始你前面有10个人,同步方式下,你需要等10*15+15=165秒才能拿到吃的,在异步方式下,你只需要等10*5+15=65秒就能拿到吃的,165:55,耗时只有以前的三分之一。可见,无论对服务方(Server)还是顾客方(Client),异步方式都是好事情

能理解麦当劳的工作方式,也就难理解Web Application开发的Asynchronous Programming Model,我相信每一个成熟的Web开发语言都提供了Asynchronous支持,不然没法开发出Scalable的应用来。

就ASP.NET而言,对于每个请求(Request),都从线程池(ThreadPool)里分配一个线程来处理这个请求,同步方式下,这个线程得全程服务,直到处理完,才能被分配去处理另外一个请求。如果处理请求的操作都是需要CPU运算的,同步方式无可厚非,但是一些操作是I/O,读取数据库,或者是调用另外一个web service,做这些I/O操作的时候,工作线程也不会消耗CPU,就是干等着,而此时可能还有很多请求正在排队等待处理呢,这合理吗?当然不合理。注意,线程池里面的线程数量是有上显的,假如是250,那么同时最多处理250个请求,再来请求就要排队了。

一个自然的想法是,增加线程池里的线程数量能够解决Sync方式的问题吗?一定程度上也许能缓一下燃眉之急,但绝对不是正道,属于饮鸩止渴。你要是把线程池的线程数量设为1000,真的就能同时处理1000个请求吗?我很怀疑,首先线程之间切换就消耗CPU资源,其次,每个线程都要占内存,.NET中一个线程大约占1M的栈空间,1000*1M就是1G,Holy God!

我们把麦当劳和ASP.NET做个类比,收银台好比CPU,服务员好比线程,顾客好比请求。要是使用更多的CPU,增加线程还有意义,不然,让多个服务员抢着用一个收银台肯定没有一人一个收银台快。理想情况,一个系统有K个CPU,就应该只有K个线程,不过历史原因导致现在不是这么回事,线程数一般都是CPU数的一个倍数,但这个倍数不能太大。

异步能够提高多大的效率,参考前面麦当劳的例子,不多说了。不过虽说异步是个好东西,但是也不是说就该把所有的服务都换成异步的,对于有I/O的服务才适用,如果完全就是CPU运算,使用异步就完全没有意义。好比你去银行取钱,一般就不会有异步服务,银行职员不会拿了你的存折之后给你一个条让你过一会拿钱,因为取钱的过程银行职员没有I/O操作,他/她在服务过程中要忙个不停地数钱做记录,不会等待什么操作,他/她也没空闲服务下一个顾客,所以银行里都是同步操作。当然我这么说有点绝对,假如你要取10000美元,而他/她手头没这么多钱,这时候也许会让你等一会,指派一个人去金库拿钱(I/O操作),然后他/她在你等待的时候,可以服务另一个顾客:)

讲得这么多,全都是ASync的必要性,如何Async Programming,这点文字是说不完的,而且写Async程序着实比谢Sync程序要复杂,改日再说吧。


4条评论 so far
留下评论

系统事件异步处理比较好,但是上层应用还是同步比较好写,就是所谓的外在同步,内在异步。

评论 由 Jinghua

什么叫“外在同步,内在异步”?同步还是异步,和任何软件设计一样,应该按照需求来决定,如果Web Application包含I/O操作,同步就会限制performance和throughput,所以需要异步来提高性能。“系统事件异步处理比较好”、“上层应用同步比较好”是基于什么样的需要呢?上层应用也有需要异步实现的啊,比如AJAX应用。

评论 由 Morgan

Eddie的意思是同步调用比较好写,对于做快速开发的人来说比较容易。
 
从Morgan的说法来看,对于Server来说,异步调用的主要好处是在效率方面。
但是对客户端来说,异步还有用户体验方面的好处,这就是事件响应和回调函数了。

评论 由 Li

毫无疑问,异步程序比同步程序难写得多,所谓天下没有免费的午餐,有得就比有失。
 
还是那句老话,程序设计要按照需求来决定,如果只是需要开发几十个公务员时不时访问的政务网站,Sync方式挺好,开发又快,捞钱也快:);要是开发供Internet上的巨大用户访问的网站,实在难以想象哪个应用不需要I/O操作,花点时间写Async程序比画钱买更多的硬件Server划算。在客户端,如果I/O只是读取本地一个小文件,同步也可以,用户几乎不会感觉到什么延迟;如果需要读取远程计算机的数据库,那耗时就不容太乐观了。当然对于客户端程序,即使不是I/O操作,对于耗时的CPU运算操作,也最好用Async,虽然一样耗时,但是给用户人的感觉好点。
 
 
 
 

评论 由 Morgan




留下评论