返回

Node 的特点之异步I/O

理解同步/异步

如果学过操作系统的同学应该知道同步/异步概念,如何理解这个异步哪,我们来看一个例子:

家来了客人.要为客人沏茶,需要烧水、洗茶壶和沏茶叶。 “同步”是这么做的:先烧水,等啊等水开了,洗茶壶,洗好了,沏茶叶; 他兄弟“异步”是这么做的:先烧水,烧水时我洗茶壶,最后再沏茶叶。

很明显异步的效率更高,那么Node是如何实现异步I/O的哪? 以下来自《深入浅出Node.js》 这里我们引入事件循环、观察者、请求对象和I/O线程池,这四者共同构成了Node异步I/O模型的基本要素。

1.事件循环

在进程启动时,Node便会创建一个类似于while(true)的循环,每执行一次循环体的过程我们称为Tick。每个Tick的过程就是查看是否有事件待处理,如果有,就取出事件及其相关的回调函数。如果存在关联的回调函数,就执行它们。然后进入下个循环,如果不再有事件处理,就退出进程。 tick流程.png

2.观察者

在每个Tick的过程中,如何判断是否有事件需要处理呢?这里必须要引入的概念是观察者。每个事件循环中有一个或者多个观察者,而判断是否有事件要处理的过程就是向这些观察者询问是否有要处理的事件。 如何理解这个观察者哪? 拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价,这里面拍卖师就是观察者。

3.请求对象 从JavaScript发起调用到内核执行完I/O操作的过渡过程中,存在一种中间产物,它叫做请求对象。 以最简单的Windows下fs.open()方法(根据指定路径和参数去打开一个文件并得到一个文件描述符)为例,从JS调用到内建模块通过libuv进行系统调用,实际上是调用了uv_fs_open()方法。在调用过程中,创建了一个FSReqWrap请求对象,从JS层传入的参数和方法都封装在这个请求对象中,其中我们最为关注的回调函数被设置在这个对象的oncompete_sym属性上。对象包装完毕后,将FSReqWrap对象推入线程池中等待执行。

至此,JS调用立即返回,JS线程可以继续执行后续操作。当前的I/O操作在线程池中等待执行,这就完成了异步调用的第一阶段。

4.回掉通知 回调通知是异步I/O的第二阶段。线程池中的I/O操作调用完毕后,会将获取的结果储存起来,然后通知IOCP当前对象操作已完成,并将线程归还线程池。在每次Tick的执行中,事件循环的I/O观察者会调用相关的方法检查线程池中是否有执行完的请求,如果存在,会将请求对象加入到I/O观察者的队列中,然后将其当做事件处理。

Licensed under CC BY-NC-SA 4.0