Nodejs Event Loop

Posted 前端花园

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nodejs Event Loop相关的知识,希望对你有一定的参考价值。

环境:

Mac OS 10.15.7

Nodejs v14.16.0


Nodejs 是一个单进程,单线程架构。擅长IO密集型服务。单线程如何处理IO密集型任务的秘诀就是:async io。而正是 Event Loop 使 async io 成为可能。


Event Loop 有三个相关的API:setTimeout,setImmediate,process.nextTick;和一个状态迁移规则。

上图每个 box 认为是一个 phase,每个phase 关联一个 FIFO queue of event callbacks。


Event Loop 在执行每个phase 特有的任务后,依次执行callbacks直到执行完所有callbacks,或超过 callbacks 的最大个数限制。这时Event Loop 执行下一个 phase。


注意:每个callback 可以 schedule更多的不同 phase 的 callbacks。poll task是由kernel产生加入到队列中的。


下面对各phase做简单介绍:

timers

执行setTimeout 和 setInterval schedule 的 callback。

过 threadhold 后尽快执行,而不是刚好在 threshold 时执行。

pending callbacks

执行deferred 到 下一个 event loop iteration 的 IO callback。

执行 some system operation 的 callback。

如果有过多的(超过处理最大限制)http request,未处理的部分,会 deferred 到下次 event loop 的 pending phase 处理。

idle,prepare
只内部使用
poll

获取新的IO event,执行IO event 的 callback(除了 close event)

有两个作用:

  1.  block 多久,poll for IO

  2. 处理 poll queue 中的 callback 


check
执行 setImmediate 回调
close

执行 close callback。

如果socket 或 handle 被非正常关闭,close event 会在这个phase触发,否则会在 process.nextTick 中触发。


注意:当poll queue 为空,

1. 若 setImmediatecallback则立即执行 setImmediate 的 callback,不会 block,否则 poll phase 会 block。

2. 若处理完 setImmediate,会检测 timer threadhold 是否到达,若到达则返回到 timers phase。


这里可以看到,poll phase 为空时会有两种情况,有setImmediate script时,执行 check phase,没有setImmediate script 时,检查 timer threshold,检测到会跳转为 timer phase。


基本概念介绍到这里,下面讲一些结论。


  • 如果 setTimeout(fn, 0) 和 setImmediate 在 非 IO context 中,他俩的执行先后顺序是不确定的。

  • 如果 setTimeout(fn, 0) 和 setImmediate 在 IO context 中,setImmediate 总是先执行。

  • process.nextTick 会 打断各个phase callbacks queue的调用,立即执行,因此 process.nextTick 可以向同一层级的 setTimeout 和 setImmediate加入回调。Nodejs 文档中提到的:current operation complete,指的是一个从 c++ handler 到 javascript callback 的一个 transition。

  • process.nextTick 的嵌套会立刻递归调用。


这里引入两个概念:

  • 优先级

  • 异步调用层级


如下面 

main module 的优先级最高 设为:0

process.nextTick 的优先级次之设为:1

非 IO context 下 setTimeout 和 setImmediate 优先级相同,设为:2

IO context 下 setImmediate 优先级设为:2,setTimeout优先级设为:3


event loop 会按照优先级执行同一层级的异步事件回调,然后执行下一层级的异步事件回调。


一个异步调用,认为是一个层级,两个异步调用嵌套认为是两个层级,n 个异步调用层级,认为是n个层级。

有一个例外:process.nextTick 对内部的其他异步调用(setTimeout,setImmediate)不产生额外层级。

console.log("common code 1 here");
setTimeout(() => {  console.log("setTimeout 1");}, 0);
setImmediate(() => { console.log("setImmediate 1");});
process.nextTick(() => {  console.log("nextTick 1");  });
console.log("common code 2 here");

输出:

Nodejs Event Loop


分析:

  • main module 中的 common code 1 here 和 common code 2 here (优先级0)最先执行。

  • process.nextTick 随后执行(优先级1)

  • setTimeout 和 setImmediate 最后执行

  • 所有代码在一个 event loop iteration 内完成。


如下 process.nextTick 中的 setTimeout,认为是第一层,全部代码在一个 event loop iteration 内完成

process.nextTick(() => { setTimeout(() => { console.log("setTimeout 2"); }, 0); })


如果setImmediate 内部有嵌套:

setImmediate(() => { console.log("setImmediate 1"); setImmediate(() => { console.log("setImmediate 2"); });});

则:setImmediate 2 会在第二个 event loop iteration内完成。


修改 process.nextTick部分,嵌套的两个 process.nextTick 会立即执行,并向setTimeout 的 callback queue内插入callback,所有动作还是在一个 event loop iteration内完成。

process.nextTick(() => { console.log("nextTick 1"); process.nextTick(() => { console.log("nextTick 2"); setTimeout(() => { console.log("setTimeout 2"); }, 0); });});


如果将 nextTick 2 放入setTimeout 中,timers phase 会被打断,优先执行 nextTick 2 部分


输出:

Nodejs Event Loop


console.log("common code 1 here");
setTimeout(() => { console.log("setTimeout 1"); setTimeout(() => { console.log("setTimeout 3"); }, 0);}, 0);process.nextTick(() => { console.log("nextTick 1"); process.nextTick(() => { console.log("nextTick 2"); setTimeout(() => {      console.log("setTimeout 2"); }, 0); }); });
console.log("common code 2 here");


输出:

Nodejs Event Loop


分析:

  • nextTick1 1  和 nextTick 2 连续输出。process.nextTick 的嵌套立即递归调用。

  • nextTick 2 后面的 setTimeout 和 setTimeout 1在同一层级上


const fs = require("fs");
fs.readdir(__dirname, (err, data) => { if (err) { throw err; }
setTimeout(() => { console.log("setTimeout 1"); }, 0);
setImmediate(() => {    console.log("setImmediate 1"); });
process.nextTick(() => {    console.log("nextTick 1"); });
console.log("common data");});

输出:

Nodejs Event Loop


分析:

在 IO context 中 setImmediate 的优先级比setTimeout 高,仍低于process.nextTick。


在 process.nextTick 部分加入 setTimeout,整个代码还是在一个event loop iteration 内完成。

输出:

Nodejs Event Loop


如果 setImmediate 内部再调用 setImmediate,和 process.nextTick 内的setTimeout 优先关闭不稳定。只有在第一层保证setImmediate 在 setTimeout 前执行。


const fs = require("fs");
fs.readdir(__dirname, (err, data) => { if (err) { throw err; }
setTimeout(() => { console.log("setTimeout 1"); }, 0);
setImmediate(() => { console.log("setImmediate 1"); setImmediate(() => {      console.log("setImmediate 2");    }); });
process.nextTick(() => { console.log("nextTick 1"); process.nextTick(() => { console.log("nextTick 2"); process.nextTick(() => { console.log("nextTick 3"); setTimeout(() => { console.log("setTimeout 2");        }); }); }); });
console.log("common data");});


输出:

分析:

IO context 中:

  • process.nextTick 嵌套会立即递归调用

  • process.nextTick 中的 setTimeout 和 setImmediate 2 的执行次数不确定。




process.nextTick demo


const EventEmitter = require('events'); const util = require('util');function MyEmitter() {  EventEmitter.call(this); // use nextTick to emit the event once a handler is process.nextTick(() => {  this.emit('event'); }); }util.inherits(MyEmitter, EventEmitter);const myEmitter = new MyEmitter(); myEmitter.on('event', () => { console.log('an event occurred!'); });


上面 MyEmitter constructor 中,如果 this.emit 不放在 process.nextTick 中,因此constructor 初始化时 还没有添加event 的 callback,所以没有输出。放入 nextTick 中,this.emit 的执行会延迟到 main module 代码执行完后,event loop 执行之前执行,这时event 已经注册了callback,所以有输出了。

类似的应用还有:

const server = net.createServer(); server.on('connection', (conn) => { });server.listen(8080); server.on('listening', () => { });

将 listening 事件的触发放入 process.nextTick 中,用户就可以注册 listening 的事件了。


将同步函数异步化:

function asyncWrapper(callback) { process.nextTick(callback);}


有几点需要声明下:

  • event loop 和 用户 code 共享同一个线程,而不是event loop 有单独一个线程


以上是关于Nodejs Event Loop的主要内容,如果未能解决你的问题,请参考以下文章

[未解决问题记录]python asyncio+aiohttp出现Exception ignored:RuntimeError('Event loop is closed')(代码片段

不要在nodejs中阻塞event loop

不要在nodejs中阻塞event loop

Nodejs Event Loop

NodeJS - Event Loop 模型

我已经迷失在事件环(event-loop)中了Nodejs篇