Event Loop

概述:

event loop(事件循环)是一个执行模型,在不同的地方有不同的实现。浏览器和NodeJS基于不同的技术实现了各自的Event Loop。


宏队列:

宏队列,macrotask,也叫tasks。一些异步任务的回调会依次进入macrotask queue,等待后续被调用,这些异步任务包括:

setTimeout

setInterval

setImmediate (Node独有)

requestAnimationFrame (浏览器独有)

I/O

UI rendering (浏览器独有)


微队列:

微队列,microtask,也叫jobs。另一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:

process.nextTick (Node独有)

Promise

Object.observe

MutationObserver

(注:这里只针对浏览器和NodeJS)


浏览器的Event Loop:

1.在主线程执行同步代码,完了以后

2.把微队列的排名第一的任务揪出来放到主线程执行,完了以后把微队列的排名第一的任务揪出来放到主线程执行....一直干空

3.把宏队列的排名第一的任务揪出来放到主线程执行,完了以后把微队列的排名第一的任务揪出来放到主线程执行,完了以后把微队列的排名第一的任务揪出来放到主线程执行....一直干空

323232323232........

PS:注意,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行


NodeJS中的Event Loop:

但是在浏览器中,可以认为只有一个宏队列,所有的macrotask都会被加到这一个宏队列中,但是在NodeJS中,不同的macrotask会被放置在不同的宏队列中。NodeJS的Event Loop中,执行宏队列的回调任务有6个阶段:

宏队列


各个阶段执行的任务如下:

timers阶段:这个阶段执行setTimeout和setInterval预定的callback

I/O callback阶段:执行除了close事件的callbacks、被timers设定的callbacks、setImmediate()设定的callbacks这些之外的callbacks

idle, prepare阶段:仅node内部使用

poll阶段:获取新的I/O事件,适当的条件下node将阻塞在这里

check阶段:执行setImmediate()设定的callbacks

close callbacks阶段:执行socket.on('close', ....)这些callbacks


每个阶段都有一个「先入先出队列」,这个队列存有要执行的回调函数(译注:存的是函数地址)。不过每个阶段都有其特有的使命。一般来说,当 event loop 达到某个阶段时,会在这个阶段进行一些特殊的操作,然后执行这个阶段的队列里的所有回调。

什么时候停止执行这些回调呢?下列两种情况之一会停止:

1.队列的操作全被执行完了

2.执行的回调数目到达指定的最大值

然后,event loop 进入下一个阶段,然后再下一个阶段。

一方面,上面这些操作都有可能添加计时器;另一方面,操作系统会向 poll 队列中添加新的事件,当 poll 队列中的事件被处理时可能会有新的 poll 事件进入 poll 队列。结果,耗时较长的回调函数可以让 event loop 在 poll 阶段停留很久,久到错过了计时器的触发时机。你可以在下文的 timers 章节和 poll 章节详细了解这其中的细节。


timers 阶段

计时器实际上是在指定多久以后可以执行某个回调函数,而不是指定某个函数的确切执行时间。当指定的时间达到后,计时器的回调函数会尽早被执行。如果操作系统很忙,或者 Node.js 正在执行一个耗时的函数,那么计时器的回调函数就会被推迟执行。

注意,从原理上来说,poll 阶段能控制计时器的回调函数什么时候被执行

举例来说,你设置了一个计时器在 100 毫秒后执行,然后你的脚本用了 95 毫秒来异步读取了一个文件当 event loop 进入 poll 阶段,发现 poll 队列为空(因为文件还没读完),event loop 检查了一下最近的计时器,大概还有 100 毫秒时间,于是 event loop 决定这段时间就停在 poll 阶段。在 poll 阶段停了 95 毫秒之后,fs.readFile 操作完成,一个耗时 10 毫秒的回调函数被系统放入 poll 队列,于是 event loop 执行了这个回调函数。执行完毕后,poll 队列为空,于是 event loop 去看了一眼最近的计时器(译注:event loop 发现卧槽,已经超时 95 + 10 - 100 = 5 毫秒了),于是经由 check 阶段、close callbacks 阶段绕回到 timers 阶段,执行 timers 队列里的那个回调函数。这个例子中,100 毫秒的计时器实际上是在 105 毫秒后才执行的。

PS:注意:为了防止 poll 阶段占用了 event loop 的所有时间,libuv(Node.js 用来实现 event loop 和所有异步行为的 C 语言写成的库)对 poll 阶段的最长停留时间做出了限制,具体时间因操作系统而异。


I/O callbacks 阶段

这个阶段会执行一些系统操作的回调函数,比如 TCP 报错,如果一个 TCP socket 开始连接时出现了 ECONNREFUSED 错误,一些 *nix 系统就会(向 Node.js)通知这个错误。这个通知就会被放入 I/O callbacks 队列。


poll 阶段(轮询阶段)

poll 阶段有两个功能:

1.如果发现计时器的时间到了,就绕回到 timers 阶段执行计时器的回调。

2.然后再,执行 poll 队列里的回调。

当 event loop 进入 poll 阶段,如果发现没有计时器,就会:

1.如果 poll 队列不是空的,event loop 就会依次执行队列里的回调函数,直到队列被清空或者到达 poll 阶段的时间上限。

2.如果 poll 队列是空的,就会:

        1.如果有 setImmediate() 任务,event loop 就结束 poll 阶段去往 check 阶段。

        2.如果没有 setImmediate() 任务,event loop 就会等待新的回调函数进入 poll 队列,并立即执行它。

一旦 poll 队列为空,event loop 就会检查计时器有没有到期,如果有计时器到期了,event loop 就会回到 timers 阶段执行计时器的回调。


check 阶段

这个阶段允许开发者在 poll 阶段结束后立即执行一些函数。如果 poll 阶段空闲了,同时存在 setImmediate() 任务,event loop 就会进入 check 阶段。

setImmediate() 实际上是一种特殊的计时器,有自己特有的阶段。它是通过 libuv 里一个能将回调安排在 poll 阶段之后执行的 API 实现的。

一般来说,当代码执行后,event loop 最终会达到 poll 阶段,等待新的连接、新的请求等。但是如果一个回调是由 setImmediate() 发出的,同时 poll 阶段空闲下来了,event loop就会结束 poll 阶段进入 check 阶段,不再等待新的 poll 事件。


close callbacks 阶段

如果一个 socket 或者 handle 被突然关闭(比如 socket.destroy()),那么就会有一个 close 事件进入这个阶段



NodeJS中微队列主要有2个:

1.Next Tick Queue:是放置process.nextTick(callback)的回调任务的

2.Other Micro Queue:放置其他microtask,比如Promise等

在浏览器中,也可以认为只有一个微队列,所有的microtask都会被加到这一个微队列中,但是在NodeJS中,不同的microtask会被放置在不同的微队列中。


NodeJS的Event Loop过程:

1.初始化EventLoop

2.执行脚本同步代码

2.执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务

3.开始执行macrotask宏任务,共6个阶段,先从第1个阶段开始执行相应每一个阶段macrotask中的所有任务

4.回到第二步,然后进入宏队列的第二个阶段,去执行完所有任务,完了再回到第二步,然后第三阶段.......


setImmediate 和 setTimeout :

setImmediate 和 setTimeout 很相似,但是其回调函数的调用时机却不一样。

setImmediate() 的作用是在当前 poll 阶段结束后调用一个函数。

setTimeout() 的作用是在一段时间后调用一个函数。

这两者的回调的执行顺序取决于 setTimeout 和 setImmediate 被调用时的环境。

如果 setTimeout 和 setImmediate 都是在主模块(main module)中被调用的,那么回调的执行顺序取决于当前进程的性能,这个性能受其他应用程序进程的影响。

举个例子,HTML5规范规定,setTimeout最少4毫秒延时,如果刚开始加载脚本用了5毫秒,当时间循环开始,执行宏任务的时候,tirmer阶段发现setTimeout已经超时间了,那么立即执行它。然而check阶段是排在tirmmer后面的,所以setTimeout先执行,

如果3毫秒就加载完了脚本,进入tirmer后发现setTimeout没到时间,就往下走,停留在poll阶段发现setTimeout到时间了,赶紧回到tirmer去执行,但是check阶段是必经之路,于是setlmmediate是要先执行的。

但是,如果把上面代码放到 I/O 操作的回调里,setImmediate 的回调就总是优先于 setTimeout 的回调

setImmediate 和 setTimeout setImmediate 和 setTimeout setImmediate 和 setTimeout setImmediate 和 setTimeout setImmediate 和 setTimeout 

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

推荐阅读更多精彩内容

  • 〈统一方向,形成合力〉人总是有各种不同的想法,如果每个员工都各行其是,公司将会怎样呢?每个人的力量凝聚到同一个方向...
    张伟99阅读 383评论 0 0
  • 一个人坚持写作初心是什么? 是赞赏吗? 不是。 那安卓能赞赏,苹果不能赞赏,给用户这种不同的体验好不好? 我觉得不...
    秋叶大叔阅读 291评论 2 4
  • 这是我自己写的一首歌词!我今年24虚岁了,本来今年就毕业了,可以说真正的长大成人了,可以赚钱养家,可以谈恋爱,可以...
    品山阅读 206评论 0 0