在咱们写前端代码的时候,常常会遇到的异步的一些操做,好比ajax
,setTimeout
,浏览器事件
等,虽然js执行是单线程的,可是浏览器倒是有多个线程的,例如发起ajax
请求,浏览器会另起一个http
线程。Node相似,js执行为单线程,可是遇到一些I/O操做,提供了异步的方式。javascript
关于同步和异步,请移步我以前写的一篇文章.前端
这篇文章算是对于《深刻浅出Node.js》第三章内容的一个摘录和小结:java
在了解Node.js中
的异步I/O前,先了解下关于阻塞和非阻塞的概念:ajax
阻塞I/O:在Node.js执行过程当中,若是遇到了磁盘的读写或网络请求时,通常可能会耗费比较多的处理时间,这个时候操做系统会剥夺Node.js这个线程的控制权,这时Node.js便中止了执行,等待系统内核层面完成全部的操做,当I/O操做
完成后,操做系统会再将CPU的控制权返还给Node.js这个线程,而后Node.js继续向后执行代码。这个即是阻塞的I/O
;segmentfault
非阻塞I/O:当Node.js遇到耗时的I/O时,会将这个I/O的请求交给操做系统,操做系统会立马返回当前调用的状态,此时Node.js会继续向下执行代码,不会发生等待的状况,可是Node.js会重复调用I/O操做来确认是否完成,即轮询。windows
首先得清楚,Node自身执行的模型----事件循环。在进程启动时,Node便会建立一个相似于while(true)
的循环,每执行一次循环体的过程咱们称为Tick
。每一个Tick
的过程就是查看是否有事件待处理,若是有,就去除事件及其相关的回调函数。浏览器
异步I/O
算是Node的特点,它力求在单线程上将资源分配得更加高效。异步I/O
的提出主要是为了知足单线程执行的js,当进行耗时的I/O操做
的时候,将原有等待I/O完成
的时间分配给其余须要的业务去执行。网络
每一个事件循环中有一个或者多个观察者,而判断是否有事件要处理的过程就是向这些观察者询问是否有要处理的事件。事件循环是一个典型的生产者/消费者模型
。异步I/O、网络请求等都是事件的生产者,源源不断为Node提供不一样类型的事件,这些事件被传递到对应的观察者那里,事件循环则从观察者那里取出事件并处理。
在Windows
下,这个循环基于IOCP建立
,而在*nix
下则基于多线程建立
。多线程
在通常的(非异步)的回调函数的调用中,函数能够由咱们自行去调用:异步
var forEach = function(list, callback) { for(var i = 0; i < list.length; i++) { callback(list[i], i, list); } }
可是在Node的异步I/O
过程中,回调函数并不禁开发者来调用。浏览器中的异步事件同理,具体内容请戳我。在js发起调用到内核完成I/O操做
的过渡过程中,存在一种中间产物,叫作请求对象
。
例如Node.js提供的核心fs模块,要去打开一个文件,fs.open(path, flags, mode, callback)
。js调用Node.js的核心模块,核心模块去调用C++核心模块去进行下层的操做。再调用底层代码时,建立了一个请求对象,从js层面传入的参数和callback
都被封装在这个请求对象上。其中callback
被设置在这个对象的一个属性上。
如下内容是在windows
环境下Node.js异步I/O模型
。
在windows
下,这个对象被推入线程池中等待执行。此时,js调用当即返回,由js层面发起的异步调用的第一个阶段就此结束。Node.js再次得到了CPU
的使用权,Node.js能够继续执行当前任务的后续操做。当前I/O操做
在线程池中等待执行,无论它是否阻塞I/O
,都不会影响到js线程后续执行,如此就达到了异步的目的。
请求对象
是异步I/O
过程当中的重要中间产物,全部的状态都保存在这个对象中,包括送去线程池等待执行以及I/O操做完毕后的回调处理。
线程池中的I/O操做
调用完毕以后,在每次tick
执行过程当中,会调用IOCP
提供的相关方法检查线程池中是否有执行完的请求,若是会将请求对象加入到I/O观察者队列
中,而后将其当作事件处理。
Windows
下主要经过IOCP
来向系统内核发送I/O调用
和从内核获取已完成的I/O操做
,配以事件循环,以此完成异步I/O
的过程。在Linux
下经过epoll
实现这个过程,FreeBSD
下经过kqueue
实现,Solaris
下经过Event ports
实现。不一样的是线程池在windows下由内核(IOCP)直接提供,*nix系列下有libuv执行实现。