欣赏:??????口味:法式鹅肝烹饪时间:20min本文已收录于Githubgithub.com/Geekhyt,感谢Star。事件循环事件循环的执行顺序从图中可以看出,每个事件循环包括上图中的6个阶段,接下来我们就一一解读。timersTimer定时器分为两种类型:ImmediateTimeout定时器在超时后的下一个校验阶段执行(delay参数默认值为1ms)。Timeout定时器有两种类型:IntervalTimeout阶段会执行setTimeout()和setInterval()poll阶段设置的回调定时器的执行由setTimeout()和setInterval()控制,浏览器中的API是相同的。它们的实现原理类似于异步I/O,但不需要I/O线程池的参与。两个定时器创建完成后,会被插入到定时器观察者内部的一颗红黑树中。每执行一次Tick,就会从红黑树中取出定时器对象,检查是否超过了计时时间,如果超过了时间限制就会执行它们的回调。注意:计时器的一个问题是它们不是绝对准确的(在公差范围内)。一旦某个任务在某个事件循环中占用的时间较多,再次轮到定时器执行时,时间就会受到影响。无IO处理情况通过执行上面的代码我们可以发现输出的结果是不确定的。因为setTimeout(fn,0)有几毫秒的不确定性,不能保证一定会进入timers阶段,timer能马上执行handler。有IO处理情况varfs=require('fs');fs.readFile(__filename,()=>{setTimeout(()=>{console.log('timeout');},0);setImmediate(()=>{console.log('immediate');});})//immediate//timeout此时setImmediate先于setTimeout执行,因为poll阶段执行完成后进入check阶段,而timers阶段在下一个eventloop阶段。PendingcallbacksPendingcallbacks执行大部分回调,除了close,times,setImmediate()设置的回调idle,perpare只是内部使用poll轮询获取新的I/O事件,在合适的条件下,Node.js会主要这里阻塞这个阶段的任务是当延迟时间到达时执行timers定时器的回调,处理poll队列中的事件。当事件循环进入轮询阶段,定时器没有被调用时,会发生以下两件事:1.如果轮询队列不为空,事件循环会遍历回调队列同步执行。2.如果poll队列为空,有两种情况:如果是setImmediate()回调调用,事件循环结束poll阶段,进入check阶段。如果没有被setImmediate()回调调用,事件循环将阻塞并等待回调被添加到轮询队列中执行。一旦轮询队列为空,事件循环将检查计时器是否已达到其延迟时间。如果一个或多个定时器已经达到它们的延迟时间,事件循环将回滚到定时器阶段并执行它们的回调。checkdetectionsetImmediate()设置的回调会在这个阶段执行,和上面poll阶段的第二种情况一样。如果poll队列为空,被setImmediate()回调调用,事件循环会直接进入check阶段。closecallbacks的回调函数socket.on('close',callback)会在这个阶段执行libuv。libuv为Node.js提供了完整的事件循环功能。如上图所示,Windows下,事件循环基于IOCP创建,Linux下通过epoll实现,FreeBSD下通过kqueue实现,Solaris下通过Eventports实现。让我们仔细看看上图。NetworkI/O、文件I/O、DNS的实现方式是分开的,因为它们本质上是由两套机制实现的。一会儿我们通过源码一窥它们的本质。本质上,当我们编写JavaScript代码调用Node的核心模块时,核心模块会调用C++内置模块,内置模块会通过libuv进行系统调用。libuv主要解决的问题在现实世界中,在所有不同类型的操作系统平台下都支持不同类型的I/O是非常困难的。那么为了支持跨平台I/O,更好的管理整个过程,libuv被抽象出来。简单的说,libuv抽象了一层API,可以帮助你调用各种平台和机器上的各种系统功能,包括操作文件、监听socket等,你不需要了解它们的具体实现。核心源码解读核心函数uv_run源码intuv_run(uv_loop_t*loop,uv_run_modemode){inttimeout;诠释;intran_pending;//检查循环中是否有异步任务,没有则结束。r=uv__loop_alive(loop);如果(!r)uv__update_time(循环);//事件循环while(r!=0&&loop->stop_flag==0){//更新事件阶段uv__update_time(loop);//处理定时器回调uv__run_timers(loop);//处理异步任务回调ran_pending=uv__run_pending(loop);//供内部使用uv__run_idle(loop);uv__run_prepare(循环);//uv_backend_timeout计算出来后传给uv__io_poll//如果timeout=0,那么uv__io_poll会直接跳过timeout=0;如果((mode==UV_RUN_ONCE&&!ran_pending||mode==UV_RUN_DEFAULT))timeout=uv_backend_timeout(loop);uv__io_poll(循环,超时);//检查阶段uv__run_check(loop);//关闭文件描述符等操作uv__run_closing_handles(loop);//检查循环中是否有异步任务,没有则结束。r=uv__loop_alive(loop);如果(模式==UV_RUN_ONCE||模式==UV_RUN_NOWAIT)中断;}returnr;}事件循环的真面目是一会儿。上面提到了网络I/O、文件I/O、DNS等都是通过两种机制实现的。首先,让我们看看网络I/O。它的最终调用会归于uv__io_start函数,该函数会将需要执行的I/O事件和回调放入watcher队列,uv__io_poll阶段会从watcher队列中取出事件。调用系统的接口并执行。(uv__io_poll部分代码太长,有兴趣的可以自行查看)uv__io_startvoiduv__io_start(uv_loop_t*loop,uv__io_t*w,unsignedintevents){assert(0==(events&~(POLLIN|POLLOUT|UV__POLLRDHUP|UV__POLLPRI)));断言(0!=事件);断言(w->fd>=0);断言(w->fd
