分类: 技术

NodeJS Event Loop 学习笔记

最近想要深入了解下 NodeJS 中 Event Loop 的工作机制,但网上的文章重复性较高,还派生出一些很容易混淆的概念,而且有些文章里举的例子甚至无法自圆其说。所以自己参照 Node 官方给的一篇介绍文章,和 Medium 上看到的一个系列文章,很好地介绍了 Event Loop 的工作原理,现在把学习笔记做个梳理:

  1. Node 主要通过 libuv 来实现异步机制,libuv 主要提供了两个功能:
    • Event Demultiplexer:代理 Node 发起的异步 I/O 请求,部分功能通过不同 OS 提供的异步机制(如 epoll/kqueue/IOCP 等)来执行,部分没有原生异步 API 支持的 I/O 功能,libuv 实现了一个线程池(默认线程数为4)来实现异步。应尽量避免使用线程池中的线程来执行异步,因为会造成性能问题(也可以通过设置$UV_THREADPOOL_SIZE来修改默认线程数)。
    • Event Queue:每个 cb 被称为一个 event,libuv 通过设置 Event Loop 执行机制,和多个 Event Queue 来执行异步 cb。
  2. libuv 提供的 Event Queue 主要有:
    • Expired Timer/Interval Queue:用来放入已经到期的 timers 和 intervals;
    • IO Event Queue:已经完成的 IO cb;
    • Immediate Queue:执行setImmediate添加的 cb;
    • Close Handlers Queue:Close Event cb;
  3. 除了上述 libuv 设置的4个主要的 Event Queue,Node 自身还设置了两个 Intermediate Queue 用来执行 microtask:
    • Next Tick Queue:执行process.nextTick添加的 cb,Node v0.12 之后,取消了添加process.maxTickDepth限制该类型 cb 执行的数量,只能依靠代码保证;
    • Other Microtask Queue:用来执行 resolved/rejected 的原生Promise等(注意,Q/Bluebird 等第三方 Promise 库的执行顺序和原生 Promise 不同)。
  4. Event Loop 按顺序执行 libuv 设置的4个 Queue 中的 cb,每一步会将当前队列中的 cb 执行完成,新添加到该 Queue 的 cb 会在下一轮 loop 中执行;同时,在进入每一个 Queue 之前,Event Loop 会检查 Node 设置的2个 Intermediate Queue,并按顺序执行这里两个 Queue 中的 cb,期间新添加到这两个 Queue 中的 cb 会被添加到 Queue 的末尾,在本轮执行(包括执行完 Other Microtask Queue 后,Event Loop 仍然会再 check 下 Next Tick Queue,如果有新 cb,仍会执行)。
  5. 上述的6个队列,其实对应了有些文章中的 macrotask 和 microtask,前者对应 libuv 设置的4个队列中的 cb,后者对应 Node 设置的2个队列中的 cb。
  6. timer 和 interval 设置的过期时间并不保证在该时间一定执行 cb,而是保证 cb 的执行时间最早不会早于该限制。

  1. 由于 timer 的上述特性,使得上述代码中的 timer 可能在第一轮 Event Loop 中执行,也可能在第一轮 Event Loop 中还没有准备好从而放在第二轮执行,所以代码的输出结果是不确定的。
  2. 但是 Immediate Queue 肯定在 IO Queue 之后执行,所以以下代码的输出顺序是确定的,setImmediate设置的 cb 总会先执行:

  1. Node 可以看做一个工具集合,其中包括以下几个模块:
    • Chrome V8 engine:执行 JS;
    • libuv:通过 Event Loop 执行异步操作;
    • c-ares:DNS 操作;
    • Other add-ons:http-parser/crypto/zlib 等。
  2. Event Loop 在以下3中情形下不会退出循环:
    • Queue 中仍有 cb 待执行;
    • 仍有 IO 处于 pending 状态;
    • 仍有 Close Event 的 cb 待执行。
  3. 在进入 Check Pace(setImmediate设置 cb 对应的 Queue)之前,libuv 内部还有一个 IO polling 的阶段,它会在某些特定条件下 block,等待 IO 完成;如果任何一个 Event Queue 中有 task,该阶段都不会 block;如果没有带执行的 task,在某些环境变量设置条件下,该阶段会 block 的时间为下一个 timer 待触发的时间,或者到达最大 block 时间限制,之后重新激活 Event Loop。
  4. 示例1:

执行结果:

示例2:

执行结果:

参考文章:
1. Event Loop and the Big Picture — NodeJS Event Loop Part 1
2. Timers, Immediates and Process.nextTick— NodeJS Event Loop Part 2
3. Promises, Next-Ticks and Immediates— NodeJS Event Loop Part 3
4. Handling IO — NodeJS Event Loop Part 4
5. The Node.js Event Loop, Timers, and process.nextTick()

注:转载注明出处并联系作者,本文链接:https://nodefe.com/nodejs-event-loop/

发表评论

评论