//耗时为M毫秒 getData(‘from_db‘); //耗时为N毫秒 getData(‘from_remote_api‘);
当采用异步的方式,总耗时为max(M,N),代码大致如下:
getData(‘from_db‘,function(result){ //消费时间为M }); getData(‘from_remote_api‘,function(result){ //消费时间为N });
随着应用的复杂性,情景会变成M+N+...和max(M,N,...),此时同步和异步的优劣就会更加凸显。另一方面,随着网站和应用的扩展,数据往往会分布到多台服务器上,而分布意味着M和N的值会线性增长,这也会放大异步和同步在性能上的差异。总之,IO是昂贵的,分布式IO是更昂贵的!
I/O的阻塞与非阻塞:IO对于操作系统内核而言,只有阻塞与非阻塞两种方式。阻塞模式的I/O会造成应用程序等待,直到I/O完成。同时操作系统也支持将I/O操作设置为非阻塞模式,这时应用程序的调用将可能在没有拿到真正数据时就立即返回了,为此应用程序需要多次调用才能确认I/O操作完全完成。这种重复调用判断操作是否完成的技术叫做“轮询”。
I/O的同步与异步:I/O的同步与异步出现在应用程序中。如果做阻塞I/O调用,应用程序等待调用的完成的过程就是一种同步状况。相反,I/O为非阻塞模式时,应用程序则是异步的。
为了获取完整的数据,应用程序需要重复调用IO操作来确认是否完成,叫做轮询。轮询技术主要有以下这些:
尽管epoll已经利用事件来降低了CPU的耗用,但是休眠期间CPU是闲置的,对于当前线程而言利用率不够。那么,是否有一种理想的异步I/O呢?
答案是当然有!理想的异步I/O实现如下图所示:
聪明的你想到了:我们可以通过信号或回调将数据传递给应用程序啊!
的确,Linux下提供了一种异步IO的方式(AIO),就是通过信号或回调传递数据的。
但不幸的是,只有Linux下有,而且还存在一些如系统缓存无法利用的缺陷。
所以,在现实中的异步IO是这样实现的:
我们利用多线程,主线程负责计算,IO线程负责IO操作,线程间通过信号进行通信。
聪明的你也许会问:Node不是单线程吗,如何实现多线程呢?
其实,Node底层是可以实现多线程的,只是上层提供给用户的JavaScript是单线程的。而这里,我们探讨的正是Node底层是如何实现异步IO的?
再细一点,其实Linux是利用如上的多线程创建线程池实现的,而Windows则是利用IOCP创建的。
在清楚了Node底层对异步IO的实现原理后,我们就可以进一步理解一个Node进程是如何实现异步IO的了。
注:事件循环是一个典型的生产者/消费者模型。异步I/O、网络请求是生产者,而事件循环则从观察者那里取出事件并处理。
fs.open = function(path, flags, mode, callback) { //... binding.open(pathModule._makeLong(path), stringToFlags(flags), mode, callback); };
这个函数的作用是根据指定的路径和参数去打开一个文件,从而得到一个文件描述符,是后续所有I/O操作的初始操作。
整个调用过程:JavaScript -> Node核心模块 -> C++内建模块 -> libuv系统调用
在uv_fs_open的调用过程中,Node.js创建了一个FSReqWrap请求对象。从JavaScript传入的参数和当前方法都被封装在这个请求对象中,其中回调函数则被设置在这个对象的oncomplete_sym属性上。
req_wrap->object_->Set(oncomplete_sym, callback);
对象包装完毕后,调用QueueUserWorkItem方法将这个FSReqWrap对象推入线程池中等待执行。
QueueUserWorkItem(&uv_fs_thread_proc, req, WT_EXECUTELONGFUNCTION)
QueueUserWorkItem接受三个参数,第一个是要执行的方法,第二个是方法的上下文,第三个是执行的标志。
至此,由JavaScript层面发起的异步调用第一阶段就此结束。
PostQueuedCompletionStatus((loop)->iocp, 0, 0, &((req)->overlapped)
PostQueuedCompletionStatus方法的作用是向创建的IOCP上相关的线程通信,线程根据执行状况和传入的参数判定退出。
在这一过程中,每次事件循环会调用GetQueuedCompletionStatus()方法检查线程池中是否有执行完的请求,若有,会将请求对象加入到I/O观察者的队列中,将其作为事件处理。
I/O观察者回调函数的行为就是取出请求对象的result属性作为参数,取出oncomplete_sym属性作为方法,然后调用执行,以此达到执行回调函数的目的。
注:IOCP是windows下得异步I/O解决方案
JavaScript是单线程的,但Node本身其实是多线程的,除了用户代码无法并行执行外,所有的I/O请求是可以并行执行的。事件循环是Node异步I/O实现的核心,Node通过事件驱动的方式处理请求,使得其无须为每个请求创建额外的线程,省掉了创建和销毁线程的开销。同时也因为线程数较少,不受线程上下文切换的影响,维持了Node的高性能。
原文:http://www.cnblogs.com/tangzhirong/p/6910776.html