javascript - Node.js - setTimeout 回调的触发顺序是否与注册的顺序相同?

标签 javascript node.js callback event-loop

我想知道node.js是否保证通过setTimeout安排的“过期”(准备执行)回调的执行顺序。 manual似乎声称事件循环的计时器阶段有一个 FIFO 回调队列。

在下面的示例中考虑到这一点,我预计 Node 会安排第一个回调,并在 1 秒后按照代码中指定的顺序安排剩余两个回调。现在,当第一个回调触发时,执行“停止”5 秒,这意味着当回调返回时,其他两个回调也准备好执行。

但是,当我运行该示例时,输出似乎是 first, third, second 。奇怪的是,当第二个回调的延迟时间修改为例如2001时而不是2000 ,顺序符合预期,即 first, second, third 。这是设计使然吗?

const spawnSync = require('child_process').spawnSync;

function wait(delta){
    spawnSync('sleep', [delta]);
}

setTimeout(() => {
    console.log('first');
    wait(5);
}, 2000);

wait(1);

setTimeout(() => {
    console.log('second');
}, 2000);

setTimeout(() => {
    console.log('third');
}, 4000);

最佳答案

仔细检查Node中的回调调度实现,确实看起来second的顺序是这样的。/third发布的示例中的回调并不能真正得到保证。

其原因在于 Node 如何处理 setTimeout lib/timers.js 中的回调。简而言之,这些回调存储在按相应延迟时间分组的链表中。现在,如果触发一个回调,Node 就会确定其组,标记当前时间 t_now并处理组内的所有回调。对于每一个,它都知道其注册时间 t_reg (当通过 setTimeout 注册时)和延迟时间 delta 。如果t_now - t_reg >= delta ,它调用回调。问题是t_now对于与相同延迟时间相对应的整组回调,仅计算一次。

为了说明这一点,我在 Debug模式下编译了 Node(--debug 脚本的 ./configure 选项)并将示例执行为:

NODE_DEBUG=timer node ./example.js

在我的机器上,我得到以下信息:

TIMER 38963: no 2000 list was found in insert, creating a new one
TIMER 38963: no 4000 list was found in insert, creating a new one
TIMER 38963: timeout callback 2000
TIMER 38963: now: 2069
TIMER 38963:    _idleStart = 64
first
TIMER 38963:    _idleStart = 1074
TIMER 38963: 2000 list wait because diff is 995
TIMER 38963: timeout callback 4000
TIMER 38963: now: 7075
TIMER 38963:    _idleStart = 1074
third
TIMER 38963: 4000 list empty
TIMER 38963: timeout callback 2000
TIMER 38963: now: 7076
TIMER 38963:    _idleStart = 1074
second
TIMER 38963: 2000 list empty

在这里,我们看到 firstsecond有时会安排回调t1=64t2=1074 , 分别。当 first回调已准备好在时间 T=2069 触发,执行它大约需要 5 秒。一旦这个 执行完成后,Node 在同一组回调中继续(即与相同延迟时间关联的回调),从而检查 second打回来。但是,它考虑的是当前时间,而不是执行 first 后的时间。回调不过时间T当它开始处理回调时(在 listOnTimeout 中输入 lib/timers.js 函数)。在我的机器上,自从 second回调注册时间为 1074 , 2069 - 1074小于2000的延迟时间因此 second回调未执行,而是重新安排为稍后执行,延迟时间为 995 (但是,相对于当前时间,而不是相对于 T )。

更具体地说,因为 first回调触发,我们知道T - t1 >= delta 。然而T - t2之间的关系和delta不保证。为了说明这一点,如果我删除 wait(1)打电话,我得到:

TIMER 39048: no 2000 list was found in insert, creating a new one
TIMER 39048: no 4000 list was found in insert, creating a new one
TIMER 39048: timeout callback 2000
TIMER 39048: now: 2067
TIMER 39048:    _idleStart = 66
first
TIMER 39048:    _idleStart = 67
second
TIMER 39048: 2000 list empty
TIMER 39048: timeout callback 4000
TIMER 39048: now: 7080
TIMER 39048:    _idleStart = 67
third
TIMER 39048: 4000 list empty

现在,second回调安排在t2=67 ,即2晚于 first 的时间单位打回来。现在当first火灾时间T=2067 , Node 处理与延迟时间2000相关的整组回调。如上所述,因此进入 second回调 - 在本例中为 T-t2正好等于2000这样second也“幸运地”被解雇了。然而, second 的调度回调仅延迟 1单位(例如由于调用 setTimeout 之前进行了一些无关的函数调用), third回调会更快触发。

关于javascript - Node.js - setTimeout 回调的触发顺序是否与注册的顺序相同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48614436/

相关文章:

c++ - cocos2d-x v3 CallFunc 作为参数/变量如何

jquery - 为什么jquery ajax回调函数不起作用?

javascript - 如何导航到我网站的另一个页面而不用在 php 中重新加载 header

javascript - 将多个 DataTable 序列化为 JSON

javascript - 属性在组件中的 ngOnInit() 之前被触发

javascript - BoxGeometry 未与 SphereGeometry 正确对齐

javascript - nodejs v10.3.0 : src\node_contextify. cc:629 的 gulp 任务问题:断言 `args[1]->IsString()' 失败

java - 对方法的编程调用与用户通过 UI 发起的调用 - 当应用程序必须知道差异时如何处理这两者?

javascript - Node js - 处理大量数据时出现 Promise Rejection 警告

node.js - 如何获取目录中文件的总大小?