简单-Node.js Event Loop 的理解 Timers,process.nextTick()

当Node.js启动时会初始化event loop, 每一个event loop都会包含按如下顺序六个循环阶段,

  • timer ( 这个阶段执行setTimeout(callback) and setInterval(callback)预定的callback;)
  • I/O callbacks(执行除了 close事件的callbacks、被timers(定时器,setTimeout、setInterval等)设定的callbacks、setImmediate()设定的callbacks之外的callbacks;)
  • idle,prepare 仅node内部使用;
  • poll() 获取新的I/O事件, 适当的条件下node将阻塞在这里;
    • incomming
    • connections
    • data ,etc
  • check执行setImmediate() 设定的callbacks;
  • close callbacks 比如socket.on(‘close’, callback)的callback会在这个阶段执行.
    每一个阶段都有一个装有callbackDe FIFO queue(队列),当event loop运行到一个指定的阶段时,node将执行该阶段的队列,当队列callback执行完或者执行callbacks数量超过该阶段的上限时,event loop会转入下一下阶段.
    注意上面六个阶段都不包括 process.nextTick()
    poll阶段
    poll阶段是衔接整个event loop各个阶段比较重要的阶段,为了便于后续例子的理解,本文和原文的介绍顺序不一样,本文先讲这个阶段;
    在node.js里,任何异步方法(除timer,close,setImmediate之外)完成时,都会将其callback加到poll queue里,并立即执行。

poll 阶段有两个主要的功能
1 处理poll队列(poll quenue)的事件(callback);
2 执行timers的callback,当到达timers指定的时间时;

如果event loop进入了 poll阶段,且代码未设定timer,将会发生下面情况:

  • 如果poll queue不为空,event loop将同步的执行queue里的callback,直至queue为空,或执行的callback到达系统上限;

  • 如果poll queue为空,将会发生下面情况:

    • 如果代码已经被setImmediate()设定了callback, event loop将结束poll阶段进入check阶段,并执行check阶段的queue (check阶段的queue是 setImmediate设定的)
    • 如果代码没有设定setImmediate(callback),event loop将阻塞在该阶段等待callbacks加入poll queue;

如果event loop进入了 poll阶段,且代码设定了timer:

  • 如果poll queue进入空状态时(即poll 阶段为空闲状态),event loop将检查timers,如果有1个或多个timers时间时间已经到达,event loop将按循环顺序进入 timers 阶段,并执行timer queue.

以上便是整个event loop时间循环的各个阶段运行机制,有了这层理解,我们来看几个例子
注意,例子中给出的时间在不同机器下和同一机器下不同执行时刻,其值都会有差异
example 1

解释:
当时程序启动时,event loop初始化:

1 timer阶段(无callback到达,setTimeout需要10毫秒)
2 i/o callback阶段,无异步i/o完成
3 忽略
4 poll阶段,阻塞在这里,当运行2ms时,fs.readFile完成,将其callback加入 poll队列,并执行callback, 其中callback要消耗20毫秒,等callback之行完,poll处于空闲状态,由于之前设定了timer,因此检查timers,发现timer设定时间是20ms,当前时间运行超过了该值,因此,立即循环回到timer阶段执行其callback,因此,虽然setTimeout的20毫秒,但实际是22毫秒后执行。

example 2


解释:
当时程序启动时,event loop初始化:

1 timer阶段(无callback到达,setTimeout需要10毫秒)
2 i/o callback阶段,无异步i/o完成
3 忽略
4 poll阶段,阻塞在这里,当运行5ms时,poll依然空闲,但已设定timer,且时间已到达,因此,event loop需要循环到timer阶段,执行setTimeout callback,由于从poll --> timer中间要经历check,close阶段,这些阶段也会消耗一定时间,因此执行setTimeout callback实际是7毫秒 然后又回到poll阶段等待异步i/o完成,在9毫秒时fs.readFile完成,其callback加入poll queue并执行。

setTimeout 和 setImmediate
二者非常相似,但是二者区别取决于他们什么时候被调用.

  • setImmediate 设计在poll阶段完成时执行,即check阶段;
  • setTimeout 设计在poll阶段为空闲时,且设定时间到达后执行;但其在timer阶段执行

其二者的调用顺序取决于当前event loop的上下文,如果他们在异步i/o callback之外调用,其执行先后顺序是不确定的

setTimeout(function timeout () { console.log('timeout');},0);
setImmediate(function immediate () { console.log('immediate');});

$ node timeout_vs_immediate.js
timeout
immediate

$ node timeout_vs_immediate.js
immediate
timeout

关于这点的原因
在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 = 0,则本次loop中的第一次uv_run_timer不生效,那么io_poll后先执行uv_run_check,即immediate先执行,然后等close cb执行完后,继续执行uv_run_timer

那么你说的为什么在回调中,一定是先immediate执行呢,其实也很容易理解你可以思考一下你写的场景
由于你的timeout和immediate的事件注册是在readFile的回调执行时,触发的所以必然的,在readFile的回调执行前的每一次event loop进来的uv_run_timer都不会有超时事件触发
那么当readFile执行完毕,kevent收到监听的fd事件完成后,执行了该回调,此时
1.timeout事件注册
2.immediate事件注册
3.由于readFile的回调执行完毕,那么就会从uv_io_poll中出来,此时立即执行uv_run_check,所以immediate事件被执行掉
4.最后的uv_run_timer检查timeout事件,执行timeout事件

所以你会发现,在I/O回调中注册的两者,永远都是immediately先执行

但当二者在异步i/o callback内部调用时,总是先执行setImmediate,再执行setTimeout

var fs = require('fs')fs.readFile(__filename, () => 
{ setTimeout(() => {
 console.log('timeout')
 }, 0) 
setImmediate(() => { 
console.log('immediate') 
})})
$ node timeout_vs_immediate.js
immediate
timeout

理解了event loop的各阶段顺序这个例子很好理解:因为fs.readFile callback执行完后,程序设定了timer 和 setImmediate,因此poll阶段不会被阻塞进而进入check阶段先执行setImmediate,后进入timer阶段执行setTimeout

process.nextTick()###

process.nextTick()不在event loop的任何阶段执行,而是在各个阶段切换的中间执行,即从一个阶段切换到下个阶段前执行。
![]6@4Y@3JMH(~95A`1.png](http://upload-images.jianshu.io/upload_images/1058258-4edc9f71a2b32cda.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
从poll —> check阶段,先执行process.nextTick,
nextTick1
nextTick2
然后进入check,setImmediate,
setImmediate
执行完setImmediate后,出check,进入close callback前,执行process.nextTick
nextTick3
最后进入timer执行setTimeout
setTimeout

process.nextTick()是node早期版本无setImmediate时的产物,node作者推荐我们尽量使用setImmediate。


功能上
这两个都能接受一个传入的function()作为参数,延迟执行。
但是在行为上
process.nextTick()在每轮事件循环中会将数组中的回调函数全部执行,而setImmediate()只会执行链表中的一个回调函数。
但是这就带来了一个问题,使用process.nextTick()推入的回调函数将会顺序执行,在数组中的回调函数执行完之前,都不会进入下次事件循环,如果数组中有一个回调函数执行时间很长,那么其他正在等待执行的回调函数就会处于长时间等待的状态。
因此,当我们需要避免这种I/O回调函数因为process.nextTick()而处于长时间等待的情况时,我们应该使用setImmediate()执行需要延迟执行的任务,因为它在每轮事件循环中只会执行一个回调函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容