浅谈Node 异步IO和事件循环

2020-06-17 07:11:51易采站长站整理

close callbacks: 执行close事件的callback,例如socket.on(‘close'[,fn])或者http.server.on(‘close, fn)。

ok, 这样就解释了Node是如何执行我们注册的事件, 那么还缺少一个环节, Node又是怎么把事件和IO请求对应起来呢? 这里涉及到了另外一种中间产物请求对象。

以打开一个文件为例子:


fs.open = function(path, flags, mode, callback){

//...

binding.open(pathModule._makeLong(path), stringToFlags(flags), mode, callback);

}

fs.open()的作用是根据指定路径和参数去打开一个文件,从而得到一个文件描述符,这是后续所有I/O操作的初始操作。从前面的代码中可以看到,JavaScript层面的代码通过调用C++核心模块进行下层的操作。

从JavaScript调用Node的核心模块,核心模块调用C++内建模块,内建模块通过libuv进行系统调用,这是Node里经典的调用方式。这里libuv作为封装层,有两个平台的实现,实质上是调用了uv_fs_open()方法。在uv_fs_open()的调用过程中,我们创建了一个FSReqWrap请求对象。从JavaScript层传入的参数和当前方法都被封装在这个请求对象中,其中我们最为关注的回调函数则被设置在这个对象的oncomplete_sym属性上:


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

QueueUserWorkItem()方法接受3个参数:第一个参数是将要执行的方法的引用,这里引用的uv_fs_thread_proc;第二个参数是uv_fs_thread_proc方法运行时所需要的参数;第三个参数是执行的标志。当线程池中有可用线程时,我们会调用uv_fs_thread_proc()方法。uv_fs_thread_proc()方法会根据传入参数的类型调用相应的底层函数。以uv_fs_open()为例,实际上调用fs_open()方法。

至此,JavaScript调用立即返回,由JavaScript层面发起的异步调用的第一阶段就此结束。JavaScript线程可以继续执行当前任务的后续操作。当前的I/O操作在线程池中等待执行,不管它是否阻塞I/O,都不会影响到JavaScript线程的后续执行,如此就达到了异步的目的。

请求对象是异步I/O过程中的重要中间产物,所有的状态都保存在这个对象中,包括送入线程池等待执行以及I/O操作完毕后的回调处理。

关于这一块其实个人认为不用过于细究, 大致上知道有这么一个请求对象即可, 最后总结一下整个异步IO的流程:

图引用自深入浅出NodeJs

至此, Node的整个异步Io流程都已经清晰了, 它是依赖于IO线程池epoll、事件循环、请求对象共同构成的一个管理机制。