Node.js doc说:
The order in which the timers are executed will vary depending on the context in which they are called. If both are called from within the main module, then timing will be bound by the performance of the process (which can be impacted by other applications running on the machine).
For example, if we run the following script which is not within an I/O cycle (i.e. the main module), the order in which the two timers are executed is non-deterministic, as it is bound by the performance of the process:
// timeout_vs_immediate.js setTimeout(function timeout () { console.log('timeout'); },0); setImmediate(function immediate () { console.log('immediate'); }); $ node timeout_vs_immediate.js timeout immediate $ node timeout_vs_immediate.js immediate timeout
在同一页面上,我想我了解事件循环是如何工作的,但是在这次主要运行之后,为什么偶数循环不能正确完成其工作?与 I/O 周期内有什么不同?
最佳答案
正如 Node.js 文档所说:
setimmediate-vs-settimeout The order in which the timers are executed will vary depending on the context in which they are called. If both are called from within the main module, then timing will be bound by the performance of the process (which can be impacted by other applications running on the machine). For example, if we run the following script which is not within an I/O cycle (i.e. the main module), the order in which the two timers are executed is non-deterministic, as it is bound by the performance of the process:
为什么?
node.js 中的每个事件都是由 libuv 的 uv_run()
函数驱动的。部分代码
int uv_run(uv_loop_t* loop, uv_run_mode mode) {
int timeout;
int r;
int ran_pending;
r = uv__loop_alive(loop);
if (!r)
uv__update_time(loop);
while (r != 0 && loop->stop_flag == 0) {
uv__update_time(loop);
uv__run_timers(loop);
ran_pending = uv__run_pending(loop);
uv__run_idle(loop);
uv__run_prepare(loop);
......
uv__io_poll(loop, timeout);
uv__run_check(loop);
uv__run_closing_handles(loop);
............
因此,正如node.js 文档所解释的,我们可以匹配代码中事件循环的每个阶段。
定时器相位 uv__run_timers(loop);
I/O回调 ran_pending = uv__run_pending(loop);
空闲/准备 uv__run_idle(loop); uv__run_prepare(循环);
轮询 uv__io_poll(循环,超时);
检查 uv__run_check(loop);
关闭回调 uv__run_ending_handles(loop);
多于阶段
但是如果我们在循环进入计时器阶段之前看到代码,它会调用
uv__update_time(loop);
初始化循环时间。
void uv_update_time(uv_loop_t* loop) {
uv__update_time(loop);
}
发生的情况是 uv__update_time(loop)
调用函数 uv__hrtime
UV_UNUSED(static void uv__update_time(uv_loop_t* loop)) {
/* Use a fast time source if available. We only need millisecond precision.
*/
loop->time = uv__hrtime(UV_CLOCK_FAST) / 1000000;
}
对 uv__hrtime
的调用是平台相关的,并且是 CPU 耗时的工作,因为它使系统
调用clock_gettime 。它受到计算机上运行的其他应用程序的影响。
#define NANOSEC ((uint64_t) 1e9)
uint64_t uv__hrtime(uv_clocktype_t type) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (((uint64_t) ts.tv_sec) * NANOSEC + ts.tv_nsec);
}
一旦返回,定时器阶段就会在事件循环中被调用。
void uv__run_timers(uv_loop_t* loop) {
...
for (;;) {
....
handle = container_of(heap_node, uv_timer_t, heap_node);
if (handle->timeout > loop->time)
break;
....
uv_timer_again(handle);
handle->timer_cb(handle);
}
}
如果当前循环时间大于超时时间,则运行 Timer 阶段的回调。
需要注意的另一件重要的事情是,当设置为 0
时,setTimeout
会在内部转换为 1
。
此外,由于 hr_time
返回时间以纳秒为单位,因此 timeout_vs_immediate.js
显示的这种行为现在变得更具解释性。
如果第一个循环之前的准备时间超过 1ms
,则 Timer
阶段将调用与其关联的回调。如果小于 1ms
事件循环继续到下一阶段,并在循环的检查阶段运行 setImmediate
回调,并在循环的检查阶段运行 setTimeout
循环的下一个刻度。
希望这能够澄清当从主模块内部调用 setTimeout
和 setImmediate
时,围绕它们的非确定性行为的方法。
关于javascript - 为什么nodejs事件循环在第一次运行后是不确定的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43566082/