libuv 和操作系统如何在 Node.js 中实际调度 setTimeout 和 setInterval 等定时器?我看到在计时器触发之前,节点进程没有使用 CPU。这是否意味着操作系统会安排定时器,并在定时器触发时唤醒 Node 进程?如果是这样,操作系统如何安排计时器以及硬件如何执行它?
2 回答
计时器回调作为 NodeJS 事件循环的一部分执行。当您调用setTimeout
orsetInterval
时,libuv(实现 NodeJS 事件循环的 C 库)在称为计时器堆的“最小堆”数据结构中创建一个计时器。在此数据结构中,它跟踪每个计时器到期的时间戳。
在事件循环的每次新迭代开始时,libuv 调用uv__update_time
,然后调用系统调用来获取当前时间并将当前循环时间更新到毫秒精度。(https://github.com/nodejs/node/blob/master/deps/uv/src/unix/core.c#L375)
紧随该步骤之后是事件循环迭代的第一个主要阶段,即计时器阶段。在这个阶段,libuv 调用uv__run_timers
处理所有过期定时器的回调。在这个阶段,libuv 遍历定时器堆,根据它刚刚更新的“循环时间”来识别过期的定时器uv__update_time
。然后它调用所有过期计时器的回调。
以下是 NodeJS 中事件循环实现的一个经过编辑的片段,以突出我刚刚描述的内容。
while (r != 0 && loop->stop_flag == 0) {
uv__update_time(loop);
uv__run_timers(loop);
// ...redacted for brevity...
r = uv__loop_alive(loop);
if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
break;
}
前段时间我写了一系列关于 NodeJS 事件循环的文章。我希望该系列中的这篇文章会有所帮助。https://blog.insiderattack.net/timers-immediates-and-process-nexttick-nodejs-event-loop-part-2-2c53fd511bb3
Node 在下面使用 libuv 来处理这个问题。虽然 setTimeout 有一些自己的内部管理,但它最终使用 libuv 提供的 uv_timer_t 工具。
让我们假设事件循环唯一在做的事情就是计时器。libuv 将计算轮询超时,这实际上是计时器的到期时间(在本例中)。然后事件循环将通过使用适当的系统调用(epoll_wait、kevent 等)阻止 i/o。此时由内核决定要做什么,但是当前的执行线程被阻塞,直到内核再次唤醒它,所以这里没有使用 CPU,因为什么都没有发生。
一旦超时到期,上述系统调用将返回,libuv 将处理到期的计时器和 i/o。