为什么 nodejs 事件循环在第一次运行后是不确定的?

fab*_*mlk 2 javascript event-loop node.js

Node.js文档说:

定时器的执行顺序将根据调用它们的上下文而有所不同。如果两者都从主模块中调用,则计时将受进程性能的限制(这可能会受到机器上运行的其他应用程序的影响)。

例如,如果我们运行以下不在 I/O 周期(即主模块)内的脚本,则两个计时器的执行顺序是不确定的,因为它受进程性能的约束:

// 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
Run Code Online (Sandbox Code Playgroud)

在同一页面上,我想我了解事件循环的工作原理,但是在这次主要运行之后,为什么偶数循环不能正确完成其工作?那里与 I/O 周期内有什么不同?

Ank*_*and 5

正如 Node.js 文档所说:

setimmediate-vs-settimeout 定时器的执行顺序将根据调用它们的上下文而有所不同。如果两者都从主模块中调用,则计时将受进程性能的限制(这可能会受到机器上运行的其他应用程序的影响)。例如,如果我们运行以下不在 I/O 周期(即主模块)内的脚本,则两个计时器的执行顺序是不确定的,因为它受进程性能的约束:

为什么 ?。

node.js 中的每个事件都是由uv_run()libuv的函数驱动的。部分代码

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);
    ............
Run Code Online (Sandbox Code Playgroud)

所以正如node.js Doc所解释的,我们可以在代码中匹配事件循环的每个阶段。

定时器相位 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);

多相

但是如果我们在循环进入计时器阶段之前仔细查看代码,它会调用 uv__update_time(loop); 初始化循环时间。

void uv_update_time(uv_loop_t* loop) {
   uv__update_time(loop);
}
Run Code Online (Sandbox Code Playgroud)

发生的事情是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;
}
Run Code Online (Sandbox Code Playgroud)

这个调用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);
}
Run Code Online (Sandbox Code Playgroud)

一旦返回,在事件循环中调用 Timer 阶段。

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

如果当前循环时间大于超时时间,则运行 Timer 阶段的回调。需要注意的另一件重要事情是setTimeout设置为0在内部转换为1. 同样作为hr_time以纳秒为单位的返回时间,如图所示的这种行为timeout_vs_immediate.js现在变得更具解释性。

如果第一个循环之前的准备工作超过1msTimerPhase 调用与其关联的回调。如果它小于1msEvent-loop 继续下一个阶段并setImmediate在循环的检查阶段和循环setTimeout的下一个滴答中运行回调。

但愿能阐明周围的不确定性的行为方式setTimeoutsetImmediate当两者都从主模块中调用。