Event loop 介绍(二)

event loop 通常联合Reactor设计模式一起使用。在 Node.js 中每个异步 IO 操作都对应一个 handler(也叫回调函数/callback)。当 IO 操作触发事件,相应的回调函数进入执行队列,event loop 从队列中取出 callback 执行。event loop 是单线程的,所以 callback 要尽量轻量。

Node.js

Node.js 进程启动后,先初始化 event loop,再执行 JavaScript 文件,之后通过 event loop 循环执行异步操作的 callbacks。Node.js 的 event loop 分为多个阶段(phase),如下图所示

result

每个 phase 都有一个队列,存放该 phase 需要执行的 callbacks。每个 phase 的说明可以参考Node.js 官方文档,解释得比较详细了。

libuv

libuv is a multi-platform support library with a focus on asynchronous I/O. It was primarily developed for use by Node.js, but it’s also used by Luvit, Julia, pyuv, and others.

实际上,Node.js 的 event loop 依赖 libuv实现,可以通过 libuv 了解更底层的实现。

event loop 可以说是 libuv 的核心部分,在 libuv 也可以看到跟 Node.js 一样的 phase

result

从 libuv 的代码,可以看到 event loop 的执行流程。

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

    /* Run one final update on the provider_idle_time in case uv__io_poll
     * returned because the timeout expired, but no events were received. This
     * call will be ignored if the provider_entry_time was either never set (if
     * the timeout == 0) or was already updated b/c an event was received.
     */
    uv__metrics_update_idle_time(loop);

    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;
}

从代码中可以看到,依次调用了

当然,event loop 是一种设计模式的组成部分,并未规定实现方式,这个只是 libuv/Node.js 的实现方式。作为使用者,多数情况下也不必全部了解这些 phase,可以适当简化,更容易理解 JavaScript 代码的执行顺序。

我们可以通过下图更清晰地了解 Node.js event loop 的执行流程。(以下图片和例子都来自这篇文章

result

大家可以先运行下这个代码,看是否知道执行结果。

Promise.resolve().then(() => console.log('promise1 resolved'));
Promise.resolve().then(() => console.log('promise2 resolved'));
Promise.resolve().then(() => {
    console.log('promise3 resolved');
    process.nextTick(() => console.log('next tick inside promise resolve handler'));
});
Promise.resolve().then(() => console.log('promise4 resolved'));
Promise.resolve().then(() => console.log('promise5 resolved'));
setImmediate(() => console.log('set immediate1'));
setImmediate(() => console.log('set immediate2'));

process.nextTick(() => console.log('next tick1'));
process.nextTick(() => console.log('next tick2'));
process.nextTick(() => console.log('next tick3'));

setTimeout(() => console.log('set timeout'), 0);
setImmediate(() => console.log('set immediate3'));
setImmediate(() => console.log('set immediate4'));

even loop 开始后,依次进入每个 phase,开始从当前 phase 获取 callbacks 前,如果 process.nextTick callbacks 和 Promise callbacks 不为空,先执行这两个队列的 callbacks,再执行当前 phase 的 callbacks,然后进入下一个 phase。

process.nextTick 是 Node.js 提供的功能,在 event loop 开始前,或每个 phase 开始前,如果存在 process.nextTick callbacks,会先执行 process.nextTick callbacks。

我们运行上面的代码,得到如下结果

next tick1
next tick2
next tick3
promise1 resolved
promise2 resolved
promise3 resolved
promise4 resolved
promise5 resolved
next tick inside promise resolve handler
set timeout
set immediate1
set immediate2
set immediate3
set immediate4
  1. 先执行 process.nextTick callbacks
  2. 然后执行 Promise callbacks
  3. 第3个 Promise callback 调用了 process.nextTick,当Promise callbacks 全部执行完,优先执行 process.nextTick callback
  4. 执行 timers phase callbacks
  5. 执行 check phase callbacks
© 2017 - 2023 · 记事本 · Theme Simpleness Powered by Hugo ·