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!
前端面试中,Event Loop 是个高频,但大部分人都只是背一背,只为了应付面试。
但若只是为了应付面试而大背一通,是浪费了时间、精力和脑力,也许真能搞定一两场面试,但不是上上策。
Event Loop 没那么简单,但也没那么难,多多少少都能说两句。这样的话题,肯定要往基本功上奔,长期学习,反复学习,争取每一次都能有一些更清晰、更深入的理解,化抽象为具象。
大而化之,浏览器中会讲宏任务与微任务,Node 中会讲关键的几个阶段,以及格外核心的
IO poll
。这么一讲,好像两者完全不同,但事实真的如此吗?
要回答这个问题,必须了解更多的来龙去脉,了解更多的 WHAT 和 WHY。
如此溯源而上,会发现两个关键点:
如此,有了宏任务和微任务,以及 六阶段 的说法。
站在这样的基础之上,再去分别了解,很容易建立起自己的认识:两者并无本质区别,只是在宏任务和微任务的具体调度策略有一些差异,并且 Node 11 开始,timers 和 check 阶段的调度策略也与浏览器对齐了,剩下服务端特有的阶段保持差异。
有了这样的认识之后,再来看 Node 中的三个关键阶段,并稍微深入一下,便能真的更开朗一些:IO poll 阶段是整个 loop 的入口,并且是核心的调度角色,是一个特殊的阶段。首先,此阶段会根据计算出来超时时间的来同步堵塞,去轮询接收文件和网络等 IO,然后处理这个 poll queue,然后处理 check,再处理 timers,之后又回到 poll 阶段,从而在根本上调度和决定着异步任务的执行顺序。
也因为这样,开始真正理解为什么下面这段代码在 Node 中执行时,打印顺序会带有不确定性:
而下面这段代码在 Node 中执行则不会产生打印顺序上的不确定性:
而这也是
setImmediate
价值和意义所在。到此处,就该放出这张图,仔细体会,Node 的 几个阶段,以及 Node 11 之后的变化 以及这段 libuv 中的核心代码:
最后,在终于看到 reddit 上一个讨论之后,对这个总结产生共鸣:
参考: