Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node源码粗读(9):nextTick、timers API、MicroTasks注册到执行全阶段解读 #20

Open
xtx1130 opened this issue Mar 15, 2018 · 6 comments

Comments

@xtx1130
Copy link
Owner

xtx1130 commented Mar 15, 2018

本文主要介绍nextTick、timers API、MicroTasks几类任务是在什么时候注册和执行的,也会从node 的bootstrap到evnet-loop过程做一个简单的介绍

整体流程

在这里以下面这段代码为例子,画一下整体的运行流程:

setTimeout(() => console.log('timers API'), 10)
new Promise((resolve, reject) => resolve('microtask run')).then(arg => console.log(arg))
process.nextTick(() => console.log('run next tick'))
setImmediate(() => console.log('setImmediate API'))

issue20-1
注意:

Environment::Start

在这里不做过多详细介绍了,之前的文章中介绍过很多。有一个知识点需要注意就是:Immediate是在这个阶段注册到uv_check_start中的。此Immediate其实是Environment::CheckImmediate函数,保证之后event-loop在运行的时候能在check阶段运行这个函数,进而触发immediate_callback_function实现对ImmediateList的调用。
至于idle部分,是为了在有ImmediateList的时候直接跳过poll阶段,毕竟poll是阻塞运行的。

bootstrap阶段

bootstrap阶段是node运行时候的构建阶段,也是最基础的阶段。bootstrap阶段会把整体的node架子搭起来(在这里不做详细介绍),之后运行业务代码。比如本文中的这个例子,上图中画的应该也比较清晰了,顺序执行。唯一的区别是:在执行不同API的时候,callback的去向是不一致的。从上图中也能看出来,这几个API最终的去向分别是:TimersList、microTask、immediateQueue、nextTickQueue。
其中,setTimeout在注册的时候会创建TimerWrap的实例,在创建实例的时候会初始化uv_timer,之后再通过TimerWrap.start启动uv_timer_start,开始监听,到时间触发运行回调函数:
issue20-2
nextTick在注册之后且bootstrap构建结束后运行SetupNextTick函数,从而触发nextTick的运行,而nextTick在运行之后会触发runMicroTasks(),清空bootstrap阶段的microTask:
issue20-3

event-loop阶段

在bootstrap之后便进入了event-loop。event-loop第一个阶段便是timers,在这里如果有到时间的Timer,便会触发OnTimeoutOnTimeout会触发InternalMakeCallback从而执行TimersList中的函数。而在执行完后还会触发InternalCallbackScope::Close,在这个函数中会触发nextTick,在触发nextTick后触发microTasks。setTimeout简易流程如下:
issue20-4
也正是InternalMakeCallbackInternalCallbackScope::Close使得libuv和v8紧紧的联系在了一起,一方面可以通过setTimeout来设置运行时间;另一方面又可以在setTimeout的回调中书写js代码。

by 小菜

@liximomo
Copy link

讲的很好!很清楚!

@xtx1130
Copy link
Owner Author

xtx1130 commented Mar 16, 2018

@liximomo 谢谢

@liximomo
Copy link

liximomo commented Mar 25, 2018

 setImmediate(() => {
    setTimeout(() => console.log(3), 0);
    process.nextTick(() => console.log(1));
    Promise.resolve().then(() => console.log(2));
 });

上诉代码 bootstrap 后 进入 event-loop.
进入 event-loop check 阶段,执行 setImmediate callbck, 然后 TimersList, nextTickQueue, microTask 都有内容。然后进入第二轮时间循环,按照您的图示,会按照 TimersList -> nextTickQueue -> microTask 的顺序,输出 3 1 2,可实际输出的却是 1 2 3.

同样的还有

const fs = require('fs');
fs.readFile('none-exist.js', () => {
  process.nextTick(() => console.log(1));
  Promise.resolve().then(() => console.log(2));
  setImmediate(() => console.log(3));
});

会在 bootstrap 后 进入首先 event-loop 的 poll 阶段, 按照图示,接下来应该是 check阶段, 执行输出应该是 3 1 2,但实际是 1 2 3。

请问我哪里理解的不对吗?望解答!

@xtx1130
Copy link
Owner Author

xtx1130 commented Mar 26, 2018

@liximomo 第一个问题,文中有这样一句话:

timers每个阶段都会执行InternalCallback以及InternalCallback::Close,在这里为了简化,只画了timers的流程

所以在check阶段执行完之后还会执行相应的回调流程,即nextTick->Microtasks->继续event-loop。
可能这句话引起了你的误会,前面的timers指的是setTimeout和setImmediate而后面的timers特指timers的流程。之所以前面的timers没写成event-loop是因为在poll阶段调用的函数和timers API有细微区别。

第二个问题:
这篇文章只是介绍timers API和MicroTask nextTick之间的关系,fs本来是下一篇文章想写的,你既然问到了就简单介绍一下:

会在 bootstrap 后 进入首先 event-loop 的 IO callback

首先你这句话就错了,fs所在的阶段是poll阶段,I/O callbacks阶段这个I/O主要是提供给libuv内部stream、IPC等用的。而fs中内部调用的是AsyncWrap::MakeCallback也可以实现对nextTick和MicroTasks的调用。所以你的第二个问题在poll之后,还是会调用nextTick和runMicroTasks,然后在继续执行check阶段

@liximomo
Copy link

liximomo commented Mar 26, 2018

@xtx1130 谢谢!这下清楚了。而且借助您的图我发现 setTimeout 在 node 和 浏览器中行为居然是不一致的!node 里会把 TimerList 里的回调全部执行完才触发 MicroTasks。 而浏览器在调用栈为空的时候就会执行 MicroTasks。

setTimeout(() => {
  console.log(1);
  Promise.resolve().then(() => console.log(11));
}, 10);
setTimeout(() => {
  console.log(2)
}, 10);

node 输出

1
2
11

browser 输出

1
11
2

@xtx1130
Copy link
Owner Author

xtx1130 commented Mar 26, 2018

@liximomo 浏览器是基于chromium,你要是有兴趣可以研究一下。。源码也就10个G,简洁版的好像也有2个多G...我没研究过,囧

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants