我对Node.js文档的下面一段感到困惑。
setTimeout()..。执行定时器的顺序将根据调用它们的上下文而有所不同。如果两者都是从主模块中调用的,那么定时将受到进程性能的约束(这可能会受到机器上运行的其他应用程序的影响)。 例如,如果我们运行的脚本不属于I/O周期(即主模块),那么执行这两个定时器的顺序是不确定的,因为它受进程性能的约束:
它将继续显示以下示例
// timeout_vs_immediate.js
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});$ node timeout_vs_immediate.js
timeout
immediate
$ node timeout_vs_immediate.js
immediate
timeout我不明白为什么结果是不确定的。由于timers阶段发生在check阶段之前,setTimeout调度的回调不应该总是在setImmediate调度的回调之前执行吗?我不认为事件循环中的阶段的顺序会因为上下文切换或其他原因而改变。
该文件还指出,
但是,如果在I/O周期内移动这两个调用,则始终首先执行立即回调:
好的,但是什么使所谓的"I/O周期“不同于主模块呢?
我知道有很多相关的问题,但是所有的答案都只是通过引用文档来说明这个事实,而没有解释非决定论在哪里起作用,所以我不认为这是重复的。
发布于 2022-07-01 21:51:24
实际的诀窍是在超时构造函数中,它由setTimeout调用,增加的倍数低于1 :1。因此,setTimeout(fn, 0)实际上等同于setTimeout(fn, 1)。
当libuv初始化时,它在更新内部时钟之后以计时器开始,当一个毫秒已经过去时,它将在进入轮询阶段(后面是setImmediate阶段)之前选择计时器。
另一个有趣的观察是,多个定时器也可能在setImmeadiate之前和之后运行:
setTimeout(() => console.log('timer'), 1);
setTimeout(() => console.log('timer'), 1);
setImmediate(() => console.log('immediate'));
// can produce:
// timer
// immediate
// timer这是因为setTimeout在内部调用getLibuvNow,后者将调用env->GetNow(),它不仅获得了libuv的当前时间,而且还得到了也会更新它。因此,计时器可能会在不同的时间被放入计时器队列中,因此计时器阶段只会捕获其中的一些。
好的,但是什么使所谓的"I/O周期“不同于主模块呢?
主模块在初始化libuv之前运行,而大多数其他代码将在libuv回路的轮询阶段运行。因此,主模块初始化之后是计时器阶段,而轮询阶段之后是'check handles‘阶段,其中包括运行setImmediate回调。因此,在主模块定时器中,通常在即时性(如果到期的话)之前运行,如果在回调中调度,则直接在定时器之前运行。
发布于 2022-07-01 20:34:51
我也会为可见性编写一个aswer,因为这是一个很好的问题,而且节点文档确实具有误导性,我还需要挖掘一些资源.
这个问题通过性能基准测试很好地回答了这里 (接受答案)。
但是实际的解释可以在另一个指向这篇文章的答案中找到,它更好地解释了事件循环。


另外,阅读这个答案 (这个是最好的,它通过内部代码,说明哪个部分依赖于平台和CPU的时间消耗,并生成非确定性,以及0在内部被转换为setTimeout的事实)到nodejs上提出的问题,这个问题似乎进一步解释了它。
更重要的一点是,当设置为0时,setTimeout被内部转换为1,这个对uv__hrtime的调用依赖于平台,并且在对clock_gettime进行系统调用时需要花费大量的cpu时间。它受到在机器上运行的其他应用程序的影响。如果第一个循环之前的准备时间超过1ms,则计时器阶段将调用与其相关的回调。如果它小于1ms,则事件循环继续到下一阶段,并在循环的检查阶段运行setImmediate回调,在循环的下一个滴答中运行setTimeout。
https://stackoverflow.com/questions/72834122
复制相似问题