浅谈NodeJs异步I/O-3
发布于 5 年前 作者 halu886 2602 次浏览 来自 分享

该文章阅读需要5分钟,更多文章请点击本人博客halu886

事件循环

正是事件循环,所以在Node中回调函数如此普遍。

当进程启动时,Node会创建一个while(true)的循环,每执行一次循环称为一个Tick。Tick的过程首先判断是否有事件,如果有事件则取出事件,且判断判断是否有关联回调,执行关联回调。如果没有事件则退出进程。
1

观察者

每个Tick如何判断是否有事件需要被处理呢?这里就需要引入观察者的概念了。 每个事件循环都有一个或多个监听者,判断当前Tick是否事件需要处理就是询问这些监听者。

浏览器的也存在监听者,例如一次点击或者加载一个文件。Node的事件则是网络请求,文件I/O。并且每种不同的事件都有对应的观察者。观察者将事件进行了分类。

事件循环是一个生产者/消费者模型。异步I/O,网络请求负责生产事件,事件传递到监听者中,事件循环则从观察者中取出事件并拓展。

在Windows中,这个循环基于IOCP创建,在*nix中,则是基于多线程创建。

请求对象

接下来我们将要举个例子来说明 在Windows中(基于IOCP)异步I/O从Javascript层到内核发生了什么。

对于一般的非异步I/O回调函数,有我们自行调用。

var forEach = function(list,callback){
     for(var i = 0; i < list.length;i++){
          callback(list[i],i,list);
     }
}

对于Node的异步I/O,却不由开发者调用。从发起调用后,到调用被执行,中间到底发生了什么?其实从Javascript发起调用到内核执行完异步I/O,产生了一种中间产物,请求对象。

接下来我们来一起学习一下fs.open()中的回调函数是是如何执行和调用的。

fs.open = function(path,flags,mode,callback){
     //...
     binding.open(pathModule.makeLong(path),
                    stringToFlags(flags),
                    mode,
                    callback);
}

fs.open()的功能是指定文件路径和参数打开一个文件,获取一个文件描述符,这也是后续所有异步I/O的初始操作。其实Javascript层面是调用C++核心模块,获取文件描述符。

2

从Javascript调用Node核心模块,核心模块调用C++内建模块,内建模块通过libuv系统调用,这是典型的Node的调用堆栈。libuv作为封装层,实际上调用uv_fs_open()方法。调用过程中,创建了一个FSReqWrap请求对象,所有参数和当前方法都封装在这个对象中,回调函数则被设置在这个对象中的oncomplete_sym属性中。

req_wrap->object->Set(oncomplete_sym,callback);

对象包装完毕,在Windows中调用QueueUserWorkItem()把这个对象推入线程池中等待执行

QueueUserWorkItem(&uv_fs_thread_proc,
                         req,
                         WT_EXECUTEDEFAULT)

QueueUserWorkItem()会接受三个参数,第一个参数是要被执行的方法的引用,第二个则是引用的方法的参数,第三个参数则是执行的标志。当线程池中有可用的参数时,uv_fs_thread_proc()则会通过参数的类型调用相应的底层函数,例如uv_fs_open(),其实调用的是fs_open()

至此,Javascript层面的异步I/O至此结束,然后Javascript线程接着执行当前任务的后续操作。当线程池中有空闲的线程时则执行I/O且不会阻塞Javascript线程的后续执行。

请求对象是异步I/O的重要中间产物,所有状态都保存在这个请求对象中,包括送入线程池以及I/O操纵结束的回调函数执行。

执行回调

组装好请求对象,将对象送入I/O线程池等待执行,这才是第一步。第二步才是执行回调。

线程I/O完成操作后,会将结果存储在req->result上。然后调用PostQueueCompletionStatus()通知IOCP,告知当前对象操作已经完成:

PostQueuedCompletionStatus((loop)->iocp,o,o,&((req)->overlapped))

PostQueuedCompletionStatus()方法的作用就是向IOCP提交执行状态,并将线程返还给线程池。

且在每次Tick的执行中,会调用IOCP的GetQueuedCompletetionStatus()检查线程池是否有执行完的请求。如果存在则将请求对象加入到I/O观察者的队列中,然后当作事件处理。

3

以上知识点均来自<<深入浅出Node.js>>,更多细节建议阅读书籍:-)

回到顶部