上周写了一篇关于require和module的文章,其中runMain函数中有一段代码这样的代码process._tickCallback();
,不知道你们还记得不。这个代码其实和process.nextick有关系。
基础认知 Macrotask & Microtask
这里描述的知识点长话短说。
nodejs事件循环中,其实具有2种任务队列:Macrotask和Microtask。每次事件循环只会执行一个Macrotask队列中的任务,然后将Microtask队列中的所有任务执行完成。周而复始,直到结束。
- Macrotask: setTimeout, setInterval, setImmediate, I/O, UI rendering
Microtask: process.nextTick, Promises, Object.observe, MutationObserver
每一次事件循环都会按照:timers ->poll(I/O)->check(immediates)这样的顺序遍历。
- timers阶段会执行所有达到延时的回调。
- poll阶段执行所有获取数据的IO回调。
- check阶段只执行一个最先设置的immediate回调。
- timers阶段若设置了I/O任务,并且在poll阶段前数据就已经返回,该I/O回调将会在该次event loop的poll阶段执行
- timers和poll阶段若设置了immediates的回调,并且此回调是最先设置的回调,则该回调会在该次event loop的check阶段执行。
例子:
根据现在这个结果,可以看出Microtask类型的nextTick确实比Macrotask类型的setImmediate要先执行。
上面的结论真对吗
setImmediate vs setTimeout 谁先执行
上面的例子执行多次后,会发现setImmediate有时会比setTimeout先执行。原因是什么,timers不是应该在check immediate之前遍历执行吗。其实,就算setTimeout设置的是0毫秒,当轮询在timers阶段,是会去判断timer设置的延时是否已经到达,如果到达才会执行。只要轮询足够快,在timer的延迟到达之前执行timers阶段,就不会执行settimeout,所以自然就会出现,setImmediate在setTimeout之前执行的情况。官方文档是这样说的setImmediate vs setTimeout,虽然只是简单的说了受性能的限制。
process.nextTick vs Promises.then 谁先执行
其实process.nextTick是一直会在Promises之前执行的,为什么呢?我们先看下process._tickCallback();
源码:
可以看出,所有的nextTick被存储在nextTickQueue队列中。每次循环取出一个tick,通过_combinedTickCallback函数执行nextTick。最后再执行_runMicrotasks函数(包括Promises的执行)。所以,process.nextTick永远优先于Promises执行。
Macrotask & Microtask 到底谁先执行
|
|
前面说过Macrotask比Microtask先执行,为什么这里Promise和nextTick比setTimeout先执行?其实,官方文档里面这里说的,可以将主进程作为一个Macrotask。但是,在源码Module._load(process.argv[1], null, true);process._tickCallback();
里面可以看见,主进程执行最后执行了_tickCallback,触发了Microtask队列里的任务。如果上面这段代码在I/O里面执行,那遵循事件循环遍历顺序,就能找到原因。
性能问题
Microtask队列太长,造成Macrotask较长时间无法执行,最终引起I/O问题。通过上面_tickCallback
方法的源码,我们知道nextTick设置了个最大值const kMaxCallbacksPerLoop = 1e4;
,考虑了性能方面问题。
参考:
next_tick源码