以下全部作废。
一个大神写了清晰易懂的文章:
英文原文:https://blog.insiderattack.ne...
翻译:https://zhuanlan.zhihu.com/p/...


官方文档

具体请看文档,此处做一两处重点摘抄。

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

process.nextTick()

process.nextTick() 从技术上讲不是事件循环的一部分。在每个事件阶段执行完毕,要开始下个事件阶段前,都会执行清空 nextTickQueue 。这里可以将其定义为:是底层 C/C++ 处理的一个过渡,在此期间处理一些需要执行的 JS 。

当使用不当时,将会造成一些问题。

递归地调用 process.nextTick() 将会导致事件循环无法继续,即 "starve" your I/O by making recursive process.nextTick() calls

这也说明了, process.nextTick() 中的递归调用将会马上排队,在此次 nextTick 执行期间执行,而不是等执行完下一个事件阶段后的 nextTick 期间执行。


深入阅读

问题 1

官方文档在介绍 setImmediate() vs setTimeout() 时说到

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

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

此处说到“受进程性能约束”具体是指什么?

详见:https://github.com/nodejs/hel...

摘录:

在 Node 中事件由 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);
    ............

对照官方文档可知,while 循环中的处理对应事件循环各个阶段。但在开始 uv__run_timers(loop) 定时器阶段处理之前,调用了这样一段代码 uv__update_time(loop)

而这个函数定义如下:

void uv_update_time(uv_loop_t* loop) {
   uv__update_time(loop);
}

uv__update_time 定义又如下,它调用了一个函数 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 。所以这个函数调用耗时将会受到此时机器上运行的其他程序的影响。

当代码执行到定时器阶段,如果此时已有定时器到了阈值,将会被取出来执行,因此 setImmediate setTimeout 的回调执行先后取决于当时进程性能。

另:setTimeout 即使设置等待时间为 0 ,其最小等待时间也会被转换成 1ms 。

问题 2

const timer = () => {
  setTimeout(timer, 1)
  setImmediate(() => console.log('immediate fired')) // this will never be logged
  const date = new Date()
  while (new Date() - date < 2) {}
}

timer()

老版本 Node 中嵌套调用 setImmediate 会导致事件循环一直被卡在 check 阶段,发生上述 process.nextTick() 中提到的 "starve" 问题。

将 Node 版本切换到 6.9.1 发现确实存在问题,但是后来切换到较新版本例如 8.9.4 已经没有这个问题了。


1059802125
19 声望3 粉丝