`setImmediate()` 和 `setTimeout()` 的时间如何与进程的性能相关?

nal*_*zok 6 javascript asynchronous callback event-loop node.js

我对 Node.js 文档的以下段落感到困惑。

setImmediate()setTimeout()

...定时器的执行顺序将根据调用它们的上下文而变化。如果两者都是从主模块内部调用的,那么计时将受到进程性能的约束(这可能会受到计算机上运行的其他应用程序的影响)。

例如,如果我们运行以下不在 I/O 周期(即主模块)内的脚本,则两个计时器的执行顺序是不确定的,因为它受到进程性能的约束:

接下来显示以下示例

// timeout_vs_immediate.js
setTimeout(() => {
  console.log('timeout');
}, 0);

setImmediate(() => {
  console.log('immediate');
});
Run Code Online (Sandbox Code Playgroud)
$ node timeout_vs_immediate.js
timeout
immediate

$ node timeout_vs_immediate.js
immediate
timeout
Run Code Online (Sandbox Code Playgroud)

我不明白是什么导致结果不确定。由于该timers阶段发生在该check阶段之前,因此安排的回调不应该setTimeout总是在安排的回调之前执行吗setImmediate?我认为事件循环中的阶段顺序不会因为上下文切换或其他原因而改变。

该文件还指出

但是,如果在一个 I/O 周期内移动这两个调用,则始终先执行立即回调:

好的,但是所谓的“I/O 周期”与主模块有何不同?


我知道有很多相关的问题,但所有答案都只是通过引用文档来陈述这一事实,而没有解释非确定性在哪里发挥作用,所以我认为这不是重复的。

Jon*_*lms 5

实际的技巧是在Timeout 构造函数中,它由setTimeout和 调用,它将时间从 1 以下增加到 1。因此setTimeout(fn, 0)实际上相当于setTimeout(fn, 1)

libuv 初始化时,它会在更新其内部时钟后启动计时器,并且当已经过去一毫秒时,它会在进入轮询阶段(随后是 setImmediate 阶段)之前拾取计时器。


另一个有趣的观察是,多个计时器也可能在setImeadiate 之前和之后运行:

setTimeout(() => console.log('timer'), 1);
setTimeout(() => console.log('timer'), 1);
setImmediate(() => console.log('immediate'));
// can produce:
// timer
// immediate
// timer
Run Code Online (Sandbox Code Playgroud)

那是因为内部setTimeout调用getLibuvNow,它会调用env->GetNow()并且不仅获取libuv的当前时间,而且还会更新它。因此,计时器可能会被放入具有不同到期时间的计时器队列中,因此计时器阶段只会选取其中的一些。

好的,但是所谓的“I/O 周期”与主模块有何不同?

主模块在 libuv 初始化之前运行,而大多数其他代码将在libuv 循环poll阶段运行。因此,主模块初始化之后是计时器阶段,而轮询阶段之后是“检查句柄”阶段,其中运行回调。因此,通常在主模块中,计时器会在立即数之前运行(如果它们到期),并且如果在回调中安排,立即数将在计时器之前运行。setImmediate