尽管js是单线程的,事件循环机制,经过在合适的时候把操做交给系统内核,从而容许node执行非阻塞的io操做
当操做完成时,内核告知node.js,合适的回调函数会被加入轮询队列,最终被执行。
Node.js启动的时候,初始化event loop,处理提供的脚本,脚本中可能调用异步API,调度timers,或者调用process.nextTick(),而后处理event loopnode
下图是简化的事件循环操做顺序图overview异步
┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘
图中每一个box就是一个phase,每一个phase有一个先进先出的回调函数的队列,
event loop进入了一个phase,就会执行phase中全部的操做,而后执行回调函数,直到队列耗尽了,或者回调函数执行数量到达最大数,接下来就去下一个phasesocket
由于任何一个操做均可能调度更多的操做,并且poll phase中新的事件由内核排队,因此正在轮询的事件在被处理的时候,poll事件们可能会排队。
结果:长时间的运行回调函数容许poll phase运行事件比timer的阈值更长。tcp
timers:执行由setTimeout() and setInterval()调度的回调函数ide
I/O callbacks:执行全部的回调函数,除了 close callbacks(由timers,setImmediate()调度)函数
idle, prepare:内部使用oop
poll:获取新的io事件,当合适的时候,node会阻塞在这里性能
check: setImmediate()回调函数会在这里调用ui
close callbacks: e.g. socket.on('close', ...)this
每次运行event loop,node检查是否有对任何异步io或者timers的等待,没有就关闭
timers指定阈值(threshold)以后,会执行回调函数,但threshold不是执行回调函数的确切时间(只是最短期)。
timers回调函数一旦能够执行了就会被执行。然而操做系统的调度或者其余的回调函数可能推迟它的执行。
由poll phase来控制何时timers被执行
var fs = require('fs'); function someAsyncOperation (callback) { // Assume this takes 95ms to complete fs.readFile('/path/to/file', callback); } var timeoutScheduled = Date.now(); setTimeout(function () { var delay = Date.now() - timeoutScheduled; console.log(delay + "ms have passed since I was scheduled"); }, 100); // do someAsyncOperation which takes 95 ms to complete someAsyncOperation(function () { var startCallback = Date.now(); // do something that will take 10ms... while (Date.now() - startCallback < 10) { ; // do nothing } });
一开始timer被调度,里面的回调函数执行log。
而后事件循环进入poll phase,此时队列是空的(由于fs.readFile()没有完成),因此就会等着,直到最先的timer的阈值(100)到时间,等了95 ms(还没到,毕竟定的是100),fs.readFile() 这个时候完成了,因此它的回调函数就回被加poll的队列而且被执行(执行10s),当回调函数完成了,队列又空了,因此,event loop将会看到timer的阈值(100)已经到了,
而后回到timers这个phase去执行timers的回调函数,也就是,打印出105秒
为了防止poll phase 独占耗尽 event loop,libuv 也有一个最大值(基于系统),会在超过最大值以前中止轮询更多的事件。
为系统操做(好比tcp错误类型)执行回调函数
当tcp socket尝试链接时接收到ECONNREFUSED,类unix系统将会想报道错误,要会在这个phase排队执行。
poll phase有两个功能
为到了时间的timers执行脚本,而后
处理poll队列的事件
当event loop 进入poll phase且没有timers被调度,下面的事情会发生
poll不空,
经过回调函数队列迭代的执行
poll栈是空的
若是脚本已经被setImmediate()调度,事件循环将会终止poll phase,到check phase去执行那些被调度的脚本
等着回调函数被加进队列,而后立马执行它
一旦poll空了,event loop将回检查timers有没有thresholds到了,有的话,wrap back to the timers phase,而后执行timers的回调函数
特别的 timer
在poll完成之后执行
在最小事件以后执行
执行顺序:
依赖于调用的上下文
若是都在main module ,事件会被进程的性能限制(被其余应用影响)
not within an I/O cycle:不肯定的
within an I/O cycle:immediate老是先(更好)
// timeout_vs_immediate.js setTimeout(function timeout () { console.log('timeout'); },0); setImmediate(function immediate () { console.log('immediate'); });
// timeout_vs_immediate.js var fs = require('fs') fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout') }, 0) setImmediate(() => { console.log('immediate') }) })
The Node.js Event Loop, Timers
参考: