将异步作为主要的编程方式和设计理念，Node是首个,**伴随着异步I/O的还有事件驱动和单线程，它们构成Node的基调**

与tornado的区别：

- Python的tornado框架同node机制一样，也是异步的，但是异步的条件是需要各种异步库，而node是在语言层面上都异步，如异步文件读取I/O。

与Nginx的区别：

- Nginx纯C编写，具有面向client管理连接的强大能力，但是它背后依然受限于各种同步方式的编程语言，而node却是全方位的，既可以作为服务器端去处理客户端大量并发请求，也能作为客户端向网络中的各个应用进行并发请求。

# 一.为何要异步
## 1.处理快速响应

耗时的操作阻塞了UI线程，造成UI线程不能响应用户操作，那么这个时候我们就需要一种机制，在发起耗时操作的请求之后要立即返回，不要阻塞UI线程，让UI线程可以继续响应用用户的操作。然后等耗时操作返回后，通过回调来处理耗时操作返回的结果。下面是在UI上使用同步的方式和异步的方式的示意图：

![](http://images.cnblogs.com/cnblogs_com/yuyijq/201102/20110220122224236.png)

![](http://images.cnblogs.com/cnblogs_com/yuyijq/201102/201102201222244871.png)

如果采用同步的方式，则一个操作需要耗时:`M+N+X+....`

如果采用异步的方式，则一个操作需要耗时：`max(M, N, X, ...)`

显然后者明显缩短了响应时间，而不会阻塞其他请求了.

# 二.资源分配
如果一组互不相关的任务需要完成，主流的方式有两种：

1. 单线程串行依次执行
2. 多线程并行执行

接下来分别分析其利弊：

## 1.多线程并行执行

**如果创建多线程的开销小于并行执行，那么多线程是首选**

- 优势在于：**多线程在多核CPU上能够充分利用多核CPU**
- 缺点在于：多线程在创建线程和执行期线程上下文切换的开销较大，同时面临锁，同步状态等问题

## 2.单线程串行依次执行

- 优点：这是目前编程的主流，也是最符合程序员顺序编程的思维习惯，易于表达。
- 缺点：容易阻塞，一个耗时操作可以导致后续执行的代码被阻塞，在计算机资源中,I/O和CPU计算可以并行执行，但在同步编程模型中，I/O的进行会让后续任务处于等待状态，CPU会处于空闲状态，这就造成资源不能被好好利用。

## 3.node选择方案
node选择**单线程**而原理多线程的死锁和状态同步，使用**异步I/O**而远离同步阻塞，为了弥补单线程无法利用多核CPU的缺陷，node使用**child_worker**,分配几个子worker通过工作进程高效利用CPU和I/O.

同步I/O的调用示意图：

![](http://beginman.qiniudn.com/io_block.jpg)

异步I/O的调用示意图：

![](http://beginman.qiniudn.com/io_async.jpg)

# 三.异步I/O实现现状
## 3.1 阻塞I/O和非阻塞I/O
注意：**阻塞”与"非阻塞"与"同步"与“异步"是两个不同的东西**

- **同步和异步关注的是消息通信机制**： (synchronous communication/ asynchronous communication)，同步，就是在发出一个调用时，在没有得到结果之前，该调用就不返回。但是一旦调用返回，就得到返回值了；异步则是相反，调用在发出之后，这个调用就直接返回了，所以没有返回结果

- **阻塞和非阻塞关注的是程序在等待调用结果（消息，返回值）时的状态：** 阻塞调用是指调用结果返回之前，当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前，该调用不会阻塞当前线程。

阻塞I/O会造成CPU等待I/O,浪费时间，所以异步I/O的作用就发挥了，在《深入浅出nodejs》中这样一段话

>操作系统对计算机进行了抽象，将所以输入输出设备抽象为文件。内核在进行I/O操作时，通过文件描述符进行管理，而文件描述符类似应用程序与系统内核之间的凭证。应用程序如果需要进行I/O调用，需要先打开文件描述符，然后再根据文件描述符去实现文件的数据读写。

>此处非阻塞I/O与阻塞I/O的区别在于阻塞I/O完成整个获取数据过程，而非阻塞I/O则不带数据直接返回，要获取数据，还需要通过文件描述符再次读取。

## 3.2 轮询
非阻塞I/O虽然能够及时返回，CPU的时间片可以处理其他事务，但也存在一个**缺点**： 完整的I/O并没有完成，虽然立即返回但是**返回的是当前调用状态**，获取完成数据还需要进行重复的I/O调用来判断操作是否完成，这种重复调用判断的技术叫**轮询**

这个轮询的过程是让**CPU处理状态判断**， 对CPU资源有一定的浪费，接下来展示轮询的技术。

轮询技术的**缺点在于应用程序要主动调用，会造成占用较多CPU时间片，性能较为低下**。现存的轮询技术有以下这些：

- read: 最古老，性能最低的一种，通过重复调用来检查I/O状态，此时CPU一直耗在等待状态下
- select：对read的改进，通过对文件描述符上的事件状态来进行判断。缺点是文件描述符最大的数量(1024)有限制
- poll：对select的改进，采用链表的方式避免最大数量限制，但描述符较多时，性能还是十分低下
- epoll：进入轮询时若没有检查到I/O事件，将会进行休眠，直到事件发生将其唤醒。这是当前Linux下效率最高的I/O事件通知机制
- kqueue：与epoll类似，仅是FreeBSD系统下存在

>轮询满足了非阻塞I/O确保获取完整数据的需求，但对于应用程序而言，它仍然只能算作一种同步，因为依然需要等待I/O完全返回。等待期间，CPU要么用于遍历文件描述符的状态，要么用于休眠等待事件发生。

所以这些轮询技术都不是太理想的。

## 3.3 理想的非阻塞I/O

尽管epoll已经利用**事件**来降低CPU的耗用，但是sleep期间CPU几乎是闲置的，对于当前线程而言利用率不高。

理想的异步I/O应该是应用程序发起异步调用，而不需要进行轮询，进而处理下一个任务，只需在I/O完成后通过信号或是回调将数据传递给应用程序即可。

![](http://beginman.qiniudn.com/async.png)

>幸运的是，在Linux下存在一种这种方式，它原生提供了一种异步非阻塞I/O方式（AIO）即是通过信号或回调来传递数据的.不幸的是，只有Linux下有这么一种支持，而且还有缺陷（AIO仅支持内核I/O中的O_DIRECT方式读取，导致无法利用系统缓存。参见：http://forum.nginx.org/read.php?

另一种理想的异步I/O是采用阻塞I/O： **加入多线程，将I/O操作分到多个线程上，利用线程之间的通信来模拟异步。**，但存在一些难以忍受的缺陷和bug， 可以简单的概述为：**Linux平台下没有完美的异步I/O支持**。

所幸的是，libev的作者Marc Alexander Lehmann重新实现了一个异步I/O的库：`libeio`。**libeio实质依然是采用线程池与阻塞I/O模拟出来的异步I/O**。

libeio是用C开发的高效异步IO库，填补了普通文件没有异步接口的空白, 建模风格和秉承的精神与`libev`类似。特性包括：异步的read、write、open、close、stat、unlink、fdatasync、mknod、readdir等（基本上是完整的POSIX API）。

Libeio完全基于事件库，可以容易地集成到事件库（或独立，甚至是以轮询方式）使用。Libeio非常轻便，且只依赖于POSIX线程。


那么在Windows平台下的状况如何呢？而实际上，Windows有一种独有的内核异步IO方案：`IOCP`。IOCP的思路是真正的异步I/O方案，调用异步方法，然后等待I/O完成通知。IOCP内部依旧是通过线程实现，不同在于这些线程由系统内核接手管理。IOCP的异步模型与Node.js的异步调用模型已经十分近似。

**以上两种方案(`libeio`和`IOCP`) 则正是Node.js选择的异步I/O方案**。由于Windows平台和*nix平台的差异，Node.js提供了`libuv`来作为**抽象封装层**，使得所有平台兼容性的判断都由这一层次来完成，保证上层的Node.js与下层的**libeio/libev及IOCP之间各自独立**。Node.js在编译期间会判断平台条件，选择性编译unix目录或是win目录下的源文件到目标程序中。

![](http://beginman.qiniudn.com/node_libuv.png)

# 四.node的异步
## 4.1 事件循环
`event loop`是计算机系统的一种运行机制，通过类似`while true`进行事件循环

![](http://image.beekka.com/blog/201310/2013102001.png)

简单说，就是在程序中设置两个线程：一个负责程序本身的运行，称为"主线程"；另一个负责主线程与其他进程（主要是各种I/O操作）的通信，被称为"Event Loop线程"（可以译为"消息线程"）。

![](http://image.beekka.com/blog/201310/2013102004.png)

上图主线程的绿色部分，还是表示运行时间，而橙色部分表示空闲时间。**每当遇到I/O的时候，主线程就让Event Loop线程去通知相应的I/O程序，然后接着往后运行，所以不存在红色的等待时间。等到I/O程序完成操作，Event Loop线程再把结果返回主线程。主线程就调用事先设定的回调函数，完成整个任务。**

可以看到，由于多出了橙色的空闲时间，所以主线程得以运行更多的任务，这就提高了效率。这种运行方式称为"异步模式"（asynchronous I/O）或"非堵塞模式"（non-blocking mode）。

事件循环的流程图如下：

![](http://beginman.qiniudn.com/node_event_loop.png)

## 4.2 观察者

每个事件循环中都有一个或多个观察者，向观察者询问是否有新的事件要处理。

事件处理就像厨师做菜，客人下单就相当于一个事件，客人点单就是关联的回调函数(记录客人的座位，点的哪些菜，做好的菜要送到哪)，那么厨师和客人之间的关联就是服务员，厨师每做完一道菜就向服务员问下一道要做什么菜，如果没有则继续等待或打样了(exit), 此时服务员就相当于观察者。

在node中，事件主要来源于网络请求、文件I/O等，这些事件对应的观察者有文件I/O观察者、网络I/O观察者等。

## 4.3 请求对象
以node中的异步I/O调用举例，node与底层之间是如何执行异步I/O调用以及回调函数究竟是如何被调用执行的：

	fs.read(path,function(data){  
    	console.info(data);  
    }

流程如下：

1. js调用node核心模块
2. node核心模块调用C++内建模块
3. C++内建模块通过libuv进行系统调用
4. 生成事件调用请求对象，将js层传入的事件(如fs.read)的方法、参数和回调函数都会被封装在请求对象中。
5. 主线程将事件封装后请求对象后压入线程池中等待执行
6. 线程立刻返回去做其他的任务

整个调用流程走过后，现在I/O事件和node的主线程已经没有关系了，不管它是否阻塞I/O，都不会影响到node的主线程，这样就达到了异步的目的。到这，由js层面发起的I/O异步调用的第一阶段已经结束。那么下面完成了I/O操作的第二个部分：回调通知

## 4.4 执行回调

线程池中的I/O操作完成之后，会将执行完成的状态存储在请求对象的result属性上并通知IOCP调用执行完成然后归还线程。事件循环每次Tick的时候，会调用IOCP相关的方法检查线程池中是否有执行完的请求，如果存在，会将请求对象加入到I/O观察者队列中，然后去请求对象执行的结果和之前注册的回调函数运行。

至此，整个异步I/O的执行流程结束，完整的流程图如下（注：linux下通过`epoll`完成）：

![](http://img.blog.csdn.net/20141212001545828)

## 4.5 总结
**事件循环，观察者，请求对象，I/O线程池这四者构成了Node异步I/O模型的基本要素**，对于构建一个高性能异步服务器，node流程如下：

![](http://7xiz2r.com1.z0.glb.clouddn.com/Screenshot%20from%202015-05-21%2012:24:32.png)

Python的Twisted也是基于事件驱动的框架，但是一直不死不活，**主要原因就在于同步I/O的存在，它的标准I/O都是阻塞式的，一旦事件循环中存在阻塞I/O，将导致其余I/O无法立即进行，性能急剧下降，其效果同同步服务一样，其他请求将不能立即处理.**

[@朴灵](http://www.zhihu.com/people/po-ling)在知乎上回答：[为什么 Node.js 这么火，而同样异步模式 Python 框架 Twisted 却十几年一直不温不火？](http://www.zhihu.com/question/23854296)：

>Node.js能做成今天这样的局面，而Python里没有这样的子原因主要有如下：

>同步IO对异步框架的性能相当致命，业务里没准就会出现一个同步IO操作。**Node解决这个问题靠的是强制异步IO操作**，使得Event driven模型能够高效执行。但是Python里用同步IO应该是很习以为常的事情，要求全异步估计很少人能做到。这就导致了tornado之类的框架无法流行。但**Node里直接剥夺了你写同步IO的权利**，并且Node没有啥历史包袱，推动异步IO比较容易。

>另一个原因如 @Belleve 所说，js里的闭包实现这个很自然。

其实我认为两者比较性较小，主要在于语言哲学上，Python自由，简洁，node则是强制性的且依赖C++,总而言之，在某些方面用比较高效的东西，我可没有语言之争 :-D



# 五.参考：

- 《深入浅出NodeJs》
- [.NET中的异步编程（一）-为什么需要异步](http://www.cnblogs.com/yuyijq/archive/2011/02/20/1958925.html)
- [3.2.1　I/O资源的使用（2）](http://book.51cto.com/art/201208/353476.htm)
- [什么是 Event Loop？](http://www.ruanyifeng.com/blog/2013/10/event_loop.html)

