彻底搞懂nodejs事件循环 Event Loop
2017-09-05 11:01
465 查看
当Node.js启动时会初始化event loop, 每一个event loop都会包含按如下顺序六个循环阶段,
timers 阶段: 这个阶段执行setTimeout(callback) and setInterval(callback)预定的callback;
I/O callbacks 阶段: This phase executes
callbacks for some system operations such as types of TCP errors. For example if a TCP socket receives
attempting to connect, some *nix systems want to wait to report the error. This will be queued to execute in the I/O callbacks phase;
idle, prepare 阶段: 仅node内部使用;
poll 阶段: 获取新的I/O事件, 适当的条件下node将阻塞在这里;
check 阶段: 执行setImmediate() 设定的callbacks;
close callbacks 阶段: 比如socket.on(‘close’, callback)的callback会在这个阶段执行.
event loop按顺序执行上面的六个阶段,每一个阶段都有一个装有callbacks的fifo queue(队列),当event loop运行到一个指定阶段时,node将执行该阶段的fifo queue(队列),当队列callback执行完或者执行callbacks数量超过该阶段的上限时,event loop会转入下一下阶段.
那么我们平常的异步io是在哪个阶段执行的呢,答案是poll阶段。
poll阶段
在node.js里,除了上面几个特定阶段的callback之外,任何异步方法完成时,都会将其callback加到poll queue里。
当event loop到poll阶段时,且不存在timer,将会发生下面的情况
如果poll queue不为空,event loop将同步的执行queue里的callback,直至queue为空,或执行的callback到达系统上限;
如果poll queue为空,将会发生下面情况:
如果代码已经被setImmediate()设定了callback 或者有满足close callbacks阶段的callback, event loop将结束poll阶段进入check阶段,并执行check阶段的queue (check阶段的queue是 setImmediate设定的)
如果代码没有设定setImmediate(callback)或者没有满足close callbacks阶段的callback,event
loop将阻塞在该阶段等待callbacks加入poll queue;
当event loop到poll阶段时,如果存在timer并且timer未到超时时间,将会发生下面情况:
则会把最近的一个timer剩余超时时间作为参数传入io_poll()中,这样event
loop 阻塞在poll阶段等待时,如果没有任何I/O事件触发,也会由timerout触发跳出等待的操作,结束本阶段,然后在close callbacks阶段结束之后会在进行一次timer超时判断
所以实际上,timer检查会发生在两个地方:timers阶段和close callbacks阶段结束之后。
贴一段别人注释的源代码
setTimeout 和 setImmediate
因为在node中,setTimeout(cb, 0) === setTimeout(cb, 1);而setImmediately属于uv_run_check的部分确实每次loop进来,都是先检查uv_run_timer的,但是由于cpu工作耗费时间,比如第一次获取的hrtime为0那么setTimeout(cb, 1),超时时间就是loop->time = 1(ms,node定时器精确到1ms,但是hrtime是精确到纳秒级别的)所以第一次loop进来的时候就有两种情况:
但当二者在异步i/o callback内部调用时,总是先执行setImmediate,再执行setTimeout
因为fs.readFile callback执行完后,程序设定了timer 和 setImmediate,因此poll阶段不会被阻塞进而进入check阶段先执行setImmediate,最后close callbacks阶段结束后检查timer,执行timeout事件
Process.nexTick()
process.nextTick()不在event loop的任何阶段执行,而是在各个阶段切换的中间执行,即从一个阶段切换到下个阶段前执行。
从poll —> check阶段,先执行process.nextTick,nextTick1,nextTick2。然后进入check执行setImmediate,setImmediate执行完后,出check,进入close callback前,执行process.nextTick ,nextTick3。最后进入timer执行setTimeout
process.nextTick()是node早期版本无setImmediate时的产物,node作者推荐我们尽量使用setImmediate。
参考文章:
https://cnodejs.org/topic/57d68794cb6f605d360105bf
https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#i-o-callbacks
┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘
timers 阶段: 这个阶段执行setTimeout(callback) and setInterval(callback)预定的callback;
I/O callbacks 阶段: This phase executes
callbacks for some system operations such as types of TCP errors. For example if a TCP socket receives
ECONNREFUSEDwhen
attempting to connect, some *nix systems want to wait to report the error. This will be queued to execute in the I/O callbacks phase;
idle, prepare 阶段: 仅node内部使用;
poll 阶段: 获取新的I/O事件, 适当的条件下node将阻塞在这里;
check 阶段: 执行setImmediate() 设定的callbacks;
close callbacks 阶段: 比如socket.on(‘close’, callback)的callback会在这个阶段执行.
event loop按顺序执行上面的六个阶段,每一个阶段都有一个装有callbacks的fifo queue(队列),当event loop运行到一个指定阶段时,node将执行该阶段的fifo queue(队列),当队列callback执行完或者执行callbacks数量超过该阶段的上限时,event loop会转入下一下阶段.
那么我们平常的异步io是在哪个阶段执行的呢,答案是poll阶段。
poll阶段
在node.js里,除了上面几个特定阶段的callback之外,任何异步方法完成时,都会将其callback加到poll queue里。
当event loop到poll阶段时,且不存在timer,将会发生下面的情况
如果poll queue不为空,event loop将同步的执行queue里的callback,直至queue为空,或执行的callback到达系统上限;
如果poll queue为空,将会发生下面情况:
如果代码已经被setImmediate()设定了callback 或者有满足close callbacks阶段的callback, event loop将结束poll阶段进入check阶段,并执行check阶段的queue (check阶段的queue是 setImmediate设定的)
如果代码没有设定setImmediate(callback)或者没有满足close callbacks阶段的callback,event
loop将阻塞在该阶段等待callbacks加入poll queue;
当event loop到poll阶段时,如果存在timer并且timer未到超时时间,将会发生下面情况:
则会把最近的一个timer剩余超时时间作为参数传入io_poll()中,这样event
loop 阻塞在poll阶段等待时,如果没有任何I/O事件触发,也会由timerout触发跳出等待的操作,结束本阶段,然后在close callbacks阶段结束之后会在进行一次timer超时判断
所以实际上,timer检查会发生在两个地方:timers阶段和close callbacks阶段结束之后。
贴一段别人注释的源代码
//deps/uv/src/unix/core.c int uv_run(uv_loop_t *loop, uv_run_mode mode) { int timeout; int r; int ran_pending; //uv__loop_alive返回的是event loop中是否还有待处理的handle或者request //以及closing_handles是否为NULL,如果均没有,则返回0 r = uv__loop_alive(loop); //更新当前event loop的时间戳,单位是ms if (!r) uv__update_time(loop); while (r != 0 && loop->stop_flag == 0) { //使用Linux下的高精度Timer hrtime更新loop->time,即event loop的时间戳 uv__update_time(loop); //执行判断当前loop->time下有无到期的Timer,显然在同一个loop里面timer拥有最高的优先级 uv__run_timers(loop); //判断当前的pending_queue是否有事件待处理,并且一次将&loop->pending_queue中的uv__io_t对应的cb全部拿出来执行 ran_pending = uv__run_pending(loop); //实现在loop-watcher.c文件中,一次将&loop->idle_handles中的idle_cd全部执行完毕(如果存在的话) uv__run_idle(loop); //实现在loop-watcher.c文件中,一次将&loop->prepare_handles中的prepare_cb全部执行完毕(如果存在的话) uv__run_prepare(loop); timeout = 0; //如果是UV_RUN_ONCE的模式,并且pending_queue队列为空,或者采用UV_RUN_DEFAULT(在一个loop中处理所有事件),则将timeout参数置为 //最近的一个定时器的超时时间,防止在uv_io_poll中阻塞住无法进入超时的timer中 if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT) timeout = uv_backend_timeout(loop); //进入I/O处理的函数(重点分析的部分),此处挂载timeout是为了防止在uv_io_poll中陷入阻塞无法执行timers;并且对于mode为 //UV_RUN_NOWAIT类型的uv_run执行,timeout为0可以保证其立即跳出uv__io_poll,达到了非阻塞调用的效果 uv__io_poll(loop, timeout); //实现在loop-watcher.c文件中,一次将&loop->check_handles中的check_cb全部执行完毕(如果存在的话) uv__run_check(loop); //执行结束时的资源释放,loop->closing_handles指针指向NULL uv__run_closing_handles(loop); if (mode == UV_RUN_ONCE) { //如果是UV_RUN_ONCE模式,继续更新当前event loop的时间戳 uv__update_time(loop); //执行timers,判断是否有已经到期的timer uv__run_timers(loop); } r = uv__loop_alive(loop); //在UV_RUN_ONCE和UV_RUN_NOWAIT模式中,跳出当前的循环 if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT) break; } //标记当前的stop_flag为0,表示当前的loop执行完毕 if (loop->stop_flag != 0) loop->stop_flag = 0; //返回r的值 return r; }
setTimeout 和 setImmediate
setTimeout(function timeout () { console.log('timeout'); },0); setImmediate(function immediate () { console.log('immediate'); });上述二者的执行顺序是不确定的。
因为在node中,setTimeout(cb, 0) === setTimeout(cb, 1);而setImmediately属于uv_run_check的部分确实每次loop进来,都是先检查uv_run_timer的,但是由于cpu工作耗费时间,比如第一次获取的hrtime为0那么setTimeout(cb, 1),超时时间就是loop->time = 1(ms,node定时器精确到1ms,但是hrtime是精确到纳秒级别的)所以第一次loop进来的时候就有两种情况:
1.由于第一次loop前的准备耗时超过1ms,当前的loop->time >=1 ,则uv_run_timer生效,timeout先执行 2.由于第一次loop前的准备耗时小于1ms,当前的loop->time < 1,则本次loop中的第一次uv_run_timer不生效,那么io_poll后先执行uv_run_check,即immediate先执行,然后等close cb执行完后,继续执行uv_run_timer
但当二者在异步i/o callback内部调用时,总是先执行setImmediate,再执行setTimeout
var fs = require('fs') fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout') }, 0) setImmediate(() => { console.log('immediate') }) })
因为fs.readFile callback执行完后,程序设定了timer 和 setImmediate,因此poll阶段不会被阻塞进而进入check阶段先执行setImmediate,最后close callbacks阶段结束后检查timer,执行timeout事件
Process.nexTick()
process.nextTick()不在event loop的任何阶段执行,而是在各个阶段切换的中间执行,即从一个阶段切换到下个阶段前执行。
var fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('setTimeout'); }, 0); setImmediate(() => { console.log('setImmediate'); process.nextTick(()=>{ console.log('nextTick3'); }) }); process.nextTick(()=>{ console.log('nextTick1'); }) process.nextTick(()=>{ console.log('nextTick2'); }) });执行结果:
nextTick1 nextTick2 setImmediate nextTick3 setTimeout
从poll —> check阶段,先执行process.nextTick,nextTick1,nextTick2。然后进入check执行setImmediate,setImmediate执行完后,出check,进入close callback前,执行process.nextTick ,nextTick3。最后进入timer执行setTimeout
process.nextTick()是node早期版本无setImmediate时的产物,node作者推荐我们尽量使用setImmediate。
参考文章:
https://cnodejs.org/topic/57d68794cb6f605d360105bf
https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#i-o-callbacks
相关文章推荐
- JavaScript:彻底理解同步、异步和事件循环(Event Loop)
- JavaScript:彻底理解同步、异步和事件循环(Event Loop)
- JavaScript:彻底理解同步、异步和事件循环(Event Loop)
- [转] JavaScript:彻底理解同步、异步和事件循环(Event Loop)
- JavaScript:彻底理解同步、异步和事件循环(Event Loop) (转)
- JavaScript:彻底理解同步、异步和事件循环(Event Loop)
- JavaScript:彻底理解同步、异步和事件循环(Event Loop)
- JavaScript:彻底理解同步、异步和事件循环
- js运行机制—事件循环(Event Loop)详解
- Node.js事件循环(Event Loop)和线程池详解
- nodejs事件循环
- Node.js事件循环(Event Loop)和线程池详解
- nodejs异步I/O和事件循环
- node.js入门 - 5.事件循环机制(event loop)
- 深入理解JavaScript的事件循环(Event Loop)
- JavaScript运行机制之事件循环(Event Loop)详解
- JavaScript事件循环(Event Loop)机制
- 跟我学NodeJS(四)事件循环、事件驱动
- nodejs---关于真正理解Node.js事件循环你需要了解的一切
- NodeJS 事件循环