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

标签 javascript node.js event-loop

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 循环的下一个刻度。

希望这能够澄清当从主模块内部调用 setTimeoutsetImmediate 时,围绕它们的非确定性行为的方法。

关于javascript - 为什么nodejs事件循环在第一次运行后是不确定的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43566082/

相关文章:

javascript - 选择全部 |使用切换按钮取消选择所有复选框

javascript - 根据 Laravel 中未定义的父 ID 获取 ajax 下拉列表

javascript - 为什么 WebSocket.onmessage 事件不会触发?

Node.js fs.writeFile 在 process.exit(0) 执行之前异步

javascript - NodeJS 事件循环多重请求

javascript - 如何使用 Leaflet 从图像创建 map

javascript - 用javascript中的单个反斜杠替换?

node.js - Nodejs事件循环,如何正确使用nextTick

javascript - 调用 valueOf 时的操作顺序

javascript - 使用 Phonegap : InvalidCastException 上传图片