我正在节点上运行
8.11
这个测试脚本:
let end = false;
let i = 0;
setInterval(() => { i++; }).unref();
let k = 0;
async function loop() {
k++
if (end === false)
setImmediate(loop);
}
console.time('test');
loop()
.then(() => {
setTimeout(() => {
end = true;
console.log('interval', i);
console.log('recursion', k);
console.timeEnd('test');
}, 1000);
})
输出为:
interval 997
recursion 824687
test: 1001.831ms
但是,如果我评论这两行:
// if (end === false)
// setImmediate(loop);
结果是:
interval 537
recursion 1
test: 1003.882ms
我努力研究了nodejs的阶段,但我不明白为什么
setImmediate
会影响区间函数的结果。
你有什么解释吗?
这是因为nodejs上的定时器并不精确,它们只是事件循环中的事件,因此它们无法确切知道它们何时会被触发,这就是为什么setTimeout说它至少会在您指定的时间之后执行提供。
不能依赖设置的超时间隔来执行 在确切的毫秒数之后。这是因为其他 执行阻止或保留事件循环的代码将推动 执行超时返回。唯一的保证是超时 不会早于声明的超时间隔执行。
当您设置 setImmediate 时,您将用大量即时事件填充事件循环,因此您将超时事件推回到队列中。
请记住,事件循环周期的持续时间并不相同(精确),因此即使您认为 setImmediate 不应该影响计时器,您也会延长事件周期的持续时间。
有趣的文章:
https://nodejs.org/en/docs/guides/timers-in-node/
编辑:正如您所指出的,在您的情况下恰恰相反,使用 setImmediate 时会有更多迭代。在这种情况下,它可能与事件循环的速度有关,该速度是动态的,具体取决于程序的负载。
我已经在原始代码中添加了日志记录
let runSetImmediate = false; // setInterval 841 setImmediate 0
// let runSetImmediate = true; // setInterval 998 setImmediate 52751
const start = Date.now();
const log = [];
let i = 0;
setInterval(() => {
i++;
log.push('i ' + i + ' ' + (Date.now() - start));
}).unref();
let k = 0;
async function loop() {
if (runSetImmediate) {
k++;
log.push('- ' + k + ' ' + (Date.now() - start));
setImmediate(loop);
}
}
console.time("test");
loop().then(() => {
setTimeout(() => {
runSetImmediate = false;
console.timeEnd("test");
console.log("setInterval", i, "setImmediate", k);
for (const line of log) {
console.log(line);
}
}, 1000);
});
现在很容易看出,
setInterval
(即当runSetImmediate = false
时)单独执行不会早于间隔,在当前版本的 Node.js 中默认为 1ms。正如https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick#timers所述,
计时器指定阈值在此之后提供的回调可以被执行而不是人们希望执行的确切时间。计时器回调将在指定的时间过后尽早运行。
在我的机器上和我的 Node.js 版本上,实际平均间隔为 1.1-1.2 毫秒。因此,我们每秒可以获得大约 840 次(甚至问题中的 537 次)执行。
另一方面,
runSetImmediate = true
用setImmediate
的回调填充事件循环,这些回调在自己的事件循环阶段执行,没有任何最小延迟https://nodejs.org/en/docs/guides/event-loop -timers-and-nexttick#check。换句话说,setImmediate
强制事件循环尽可能快地循环,每秒数千次。
这样,事件循环的
timers
阶段会被更频繁地访问,实际间隔更接近 1 毫秒。它仍然比 1 毫秒多一点 - 持续时间为 10 秒的测量(并且删除了日志记录)在我的机器上显示其值为 1.002-1.006 毫秒。