事件多路分发器

事件多路分发器在真实世界中存在的组件,而是反应器模式中一个抽象的概念。在现实世界中,事件多路分发器在不同的系统中实现,名字不同。比如在 Linux 中 epoll,在 BSD(MacOS) 系统中是 kqueue,在 Solaris 中 event ports,在 windows 系统中是 IOCP(Input Output Completion Port)等等。NodeJS 消费被那些实现的低级,非阻塞,异步的硬件 I/O 能力。

事件队列

所有的事件入队的队列应该是一个数据结构,按顺序处理事件循环,直到队列为空。但是在 Node 中是如何发生的,完全不同于反应器模式描述的那样。因此有哪些差异?

在 NodeJS 中不止一个队列,不同类型的事件在它们自己的队列中入队。在处理完一个阶段后,移向下一个阶段之前,事件循环将会处理两个中间队列,直到两个中间队列为空。_ 那么这里有多少个队列呢?中间队列是什么?
有 4 个主要类型的队列,被原生的 libuv 事件循环处理。

  • 过期计时器和间隔队列(Expired timers and intervals queue) - 使用 setTimeout 添加的过期计时器的回调或者使用 setInterval 添加的间隔函数。
  • IO 事件队列(IO Events Queue) - 完成的 I/O 事件
  • 立即的队列(Immediate queue) - 使用 setImmediate 函数添加的回调
  • 关闭操作队列(Close Handlers Queue) - 任何一个 close 事件处理器。

    注意,尽管我在这里都简单说 “队列”,它们中的一些实际上是数据结构的不同类型(timers 被存储在最小堆里) 除了四个主要的队列,这里另外有两个有意思的队列,我之前提到的 “中间队列”,被 Node 处理。尽管这些队列不是 libuv 的一部分,但是 NodeJS 的一部分。它们是:

  • 下一个运转队列(Next Ticks Queue) - 使用 process.nextTick() 函数添加的回调

  • 其他的微队列(other Microtasks Queue) - 包含其他的微队列如成功的 Promise 回调

正如你在下面图表中所见,Node 通过检查 timers 队列中任何一个过期 timers 开始事件循环(译者注:先检查事件循环是否活着),通过每个步骤的每个队列。处理完关闭处理器队列,如果在所有的队列中没有要处理的项,那么循环将会退出。在事件循环中每个队列的处理可以看做事件循环的一个阶段。
image.png

被描绘成红色的中间队列有趣的是,只要一个阶段结束,事件循环将会检查这两个中间阶段是否有要处理的项。如果有,事件循环会立马开始处理它们直到两个队列为空。一旦为空,事件循环就移到下一个阶段,实际上 next tick queue 比 micro tasks queue 有着更高的优先级。