xxleyi / learning_list

聚集自己的学习笔记
10 stars 3 forks source link

Event Loop in Node or Browser #258

Open xxleyi opened 3 years ago

xxleyi commented 3 years ago

前端面试中,Event Loop 是个高频,但大部分人都只是背一背,只为了应付面试。

但若只是为了应付面试而大背一通,是浪费了时间、精力和脑力,也许真能搞定一两场面试,但不是上上策。

Event Loop 没那么简单,但也没那么难,多多少少都能说两句。这样的话题,肯定要往基本功上奔,长期学习,反复学习,争取每一次都能有一些更清晰、更深入的理解,化抽象为具象

大而化之,浏览器中会讲宏任务与微任务,Node 中会讲关键的几个阶段,以及格外核心的 IO poll

这么一讲,好像两者完全不同,但事实真的如此吗?

要回答这个问题,必须了解更多的来龙去脉,了解更多的 WHAT 和 WHY。

如此溯源而上,会发现两个关键点:

如此,有了宏任务和微任务,以及 六阶段 的说法。

站在这样的基础之上,再去分别了解,很容易建立起自己的认识:两者并无本质区别,只是在宏任务和微任务的具体调度策略有一些差异,并且 Node 11 开始,timers 和 check 阶段的调度策略也与浏览器对齐了,剩下服务端特有的阶段保持差异。

有了这样的认识之后,再来看 Node 中的三个关键阶段,并稍微深入一下,便能真的更开朗一些:IO poll 阶段是整个 loop 的入口,并且是核心的调度角色,是一个特殊的阶段。首先,此阶段会根据计算出来超时时间的来同步堵塞,去轮询接收文件和网络等 IO,然后处理这个 poll queue,然后处理 check,再处理 timers,之后又回到 poll 阶段,从而在根本上调度和决定着异步任务的执行顺序。

也因为这样,开始真正理解为什么下面这段代码在 Node 中执行时,打印顺序会带有不确定性:

// timeout_vs_immediate.js
setTimeout(() => {
  console.log('timeout');
}, 0);

setImmediate(() => {
  console.log('immediate');
});

而下面这段代码在 Node 中执行则不会产生打印顺序上的不确定性:

// timeout_vs_immediate.js
const fs = require('fs');

fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
});

而这也是 setImmediate 价值和意义所在。

到此处,就该放出这张图,仔细体会,Node 的 几个阶段,以及 Node 11 之后的变化 image 以及这段 libuv 中的核心代码:

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__update_time(loop);
      uv__run_timers(loop);
    }

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

最后,在终于看到 reddit 上一个讨论之后,对这个总结产生共鸣:

In short: Poll is the place you "enter" the loop. It checks the timers & events & setImmediateQueue, then kicks off executing the appropriate setImmediate -> close -> timers -> pending callbacks -> events callbacks. Repeat!

参考: