Quantcast
Channel: CNode:Node.js专业中文社区
Viewing all articles
Browse latest Browse all 14821

深入理解 setTimeout、setImmediate、process.nextTick

$
0
0

setTimeout注册的回调会在事件循环的 timerspollclosing callbacks阶段执行。需要注意的是,计时器默认定义的 TIMEOUT_MAX的取值范围是 [1, 2 ^ 31 - 1],不足 1 或者超过上限都会初始化为 1,也就是说你调用 setTimeout(fn, 0)setTimeout(fn, 1)的效果是一样的。

process.nextTick注册的回调会在事件循环的当前阶段结束前执行,而不是只有 pollcheck阶段才会执行。process是内核模块,运行时是全局上下文,所以 microtask只有一个,无论你是在哪个阶段、哪个闭包内用 nextTick注册的回调都会被 pushnextTickQueue,并在事件循环当前阶段结束前执行。

setImmediate注册的回调会在 check阶段、check阶段、check阶段执行。因为它需要由 check watcher来执行,check watcher只在 check阶段处于 active状态。与 process.nextTick不同,setImmediate因运行时的上下文不同而产生不同的 ImmediateList,所以 macrotask可以有多个。setImmediate会在异常的时候执行 process.nextTick(processImmediate),会在当前阶段结束前重新执行一次这个异常任务(即 setImmediate阶段)。

具体的执行过程参考 node/deps/uv/src/unix/core.c

// 332
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);

    timeout = 0;
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
      timeout = uv_backend_timeout(loop);

    uv__io_poll(loop, timeout);
    uv__run_check(loop);
    uv__run_closing_handles(loop);

    if (mode == UV_RUN_ONCE) {
      /* UV_RUN_ONCE implies forward progress: at least one callback must have
       * been invoked when it returns. uv__io_poll() can return without doing
       * I/O (meaning: no callbacks) when its timeout expires - which means we
       * have pending timers that satisfy the forward progress constraint.
       *
       * UV_RUN_NOWAIT makes no guarantees about progress so it's omitted from
       * the check.
       */
      uv__update_time(loop);
      uv__run_timers(loop);
    }

    r = uv__loop_alive(loop);
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }

  /* The if statement lets gcc compile it to a conditional store. Avoids
   * dirtying a cache line.
   */
  if (loop->stop_flag != 0)
    loop->stop_flag = 0;

  return r;
}

具体执行情况参见下面这个例子:

/**
 * 执行栈中注册 setTimeout 计时器
 */
setTimeout(function () {
  // 4. timers 阶段。timer watcher 遍历计时器 Map 的 key,
  // 如果有 key <= timeout,执行该 key 对应的 value(计时器任务);
  // 否则等到 poll 阶段再检查一次
  console.log('setTimeout');

  setTimeout(function () {
    // 11. 注册 setTimeout 计时器。UV_RUN_ONCE 模式下,
    // 会在循环结束之前再执行时间下限到达的计时器任务,取决于进程性能
    // 1 <= TIMEOUT_MAX <= 2 ^ 31 - 1
    console.log('setTimeout in setTimeout');
  }, 0);

  setImmediate(function () {
    // 9. 注册 setImmediate 计时器。在当前循环的 check 阶段执行。
    // (注:这是新的 ImmediateList,当前循环内有 3 个 ImmediateList 了)
    console.log('setImmediate in setTimeout');
  });
  
  process.nextTick(function () {
    // 6. 为 nextTickQueue 添加任务,timers 阶段结束前唤醒 idle watcher
    // idle watcher 检查 nextTickQueue,执行任务
    console.log('nextTick in setTimeout');
  });
}, 0);

/**
 * 执行栈中注册 setImmediate 计时器
 */
setImmediate(function () {
  // 7. poll 阶段没有可执行任务,阶段结束前唤醒 idle watcher,idle watcher 继续睡;
  // 接着唤醒 check watcher,检测到 ImmediateList 不为空,进入 check 阶段。
  // check watcher 执行第一个任务 
  console.log('setImmediate');

  setTimeout(function () {
    // 13. 注册 setTimeout 计时器
    // 由于机器性能,在循环结束前才执行
    console.log('setTimeout in setImmediate');
  }, 0);

  setImmediate(function () {
    // 12. 为当前 ImmediateList 添加任务
    // 由于机器性能优越,前面 nextTickQueue 为空了,直接进入 check 阶段
    console.log('setImmediate in setImmediate');
  });
  
  process.nextTick(function () {
    // 10. 为 nextTickQueue 添加任务,当所有 ImmediateList 的队首任务都执行完毕时,
    // 唤醒 idle watcher,检查 nextTickQueue,执行队列任务
    console.log('nextTick in setImmediate');
  });
});

/**
 * 执行栈中为 nextTickQueue 添加任务
 */
process.nextTick(function () {
  // 2. 执行栈为空,进入事件循环准备阶段,唤醒 prepare watcher,
  // 检查 nextTickQueue,执行队列中的任务
  console.log('nextTick');

  setTimeout(function () {
    // 5. 注册计时器任务,timers 阶段到达时间下限则执行该任务,
    // 否则等到 poll 阶段
    console.log('setTimeout in nextTick');
  }, 0);

  setImmediate(function () {
    // 8. 注册 setImmediate 计时器,在当前循环的 check 阶段执行。
    // (注:这是新的 ImmediateList,当前循环内有 2 个 ImmediateList 了)
    console.log('setImmediate in nextTick');
  });
  
  process.nextTick(function () {
    // 3. prepare watcher 处于活跃状态,检测 nextTickQueue 的新任务,
    // 执行完所有任务后沉睡
    console.log('nextTick in nextTick');
  });
});

console.log('main thread'); // 1. 执行栈的任务

// 输出:
// main thread
// nextTick
// nextTick in nextTick
// setTimeout
// setTimeout in nextTick
// nextTick in setTimeout
// setImmediate
// setImmediate in nextTick
// setImmediate in setTimeout
// nextTick in setImmediate
// setTimeout in setTimeout
// setImmediate in setImmediate
//setTimeout in setImmediate
/* 后面 setImmediate 注册的回调会因为进程执行性能顺序有所不同 */

Viewing all articles
Browse latest Browse all 14821

Trending Articles