You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
function Timeout(callback, after, args, isRepeat, isRefed) {
after *= 1; // Coalesce to number or NaN
if (!(after >= 1 && after <= TIMEOUT_MAX)) {
if (after > TIMEOUT_MAX) {
process.emitWarning(`${after} does not fit into` +
' a 32-bit signed integer.' +
'\nTimeout duration was set to 1.',
'TimeoutOverflowWarning');
}
after = 1; // Schedule on next tick, follows browser behavior
}
....
function setImmediate(callback, arg1, arg2, arg3) {
validateCallback(callback);
let i, args;
switch (arguments.length) {
// fast cases
case 1:
break;
case 2:
args = [arg1];
break;
case 3:
args = [arg1, arg2];
break;
default:
args = [arg1, arg2, arg3];
for (i = 4; i < arguments.length; i++) {
// Extend array dynamically, makes .apply run much faster in v6.0.0
args[i - 1] = arguments[i];
}
break;
}
return new Immediate(callback, args);
}
function insert(item, msecs, start = getLibuvNow()) {
// Truncate so that accuracy of sub-milisecond timers is not assumed.
msecs = MathTrunc(msecs);
item._idleStart = start;
// Use an existing list if there is one, otherwise we need to make a new one.
let list = timerListMap[msecs];
if (list === undefined) {
debug('no %d list was found in insert, creating a new one', msecs);
const expiry = start + msecs;
timerListMap[msecs] = list = new TimersList(expiry, msecs);
timerListQueue.insert(list);
if (nextExpiry > expiry) {
scheduleTimer(msecs);
nextExpiry = expiry;
}
}
L.append(list, item);
}
setTimeout/setImmediate
setTimeout
先看setTimeout函数:
通过后面两行代码:
首先实例化Timeout,将timeout插入队列中
在Timeout函数中
如果超时时间小于1或者大于TIMEOUT_MAX时,会将超时时间重置为1。
回到 Event Loop 中的uv__run_timers函数
总体是一个for循环,取出超时时间(这里的超时时间时经过处理的,简单理解时将超时时间加上当时时间)最小节点,判断这个超时时间,如果小于现在时间就执行回调,然后继续循环。如果超时时间大于现在时间,证明时间还没到,跳出循环,结束函数(因为是最小二叉堆,所以第一个没有超时,后面的就不需要处理)。
setImmediate
setImmediate和setTimeout差不多,只不过少了超时时间。
setImmediate函数中实例化了Immediate
在Immediate中会将this插入immediateQueue队列中。
在Event Loop事件循环中uv__run_check函数。这个函数是通过宏生成的,在loop-watcher.c文件中
在uv__run_check函数中,总体就是取出队列中的节点执行。
nextTick/Promise 执行时机(以setTimeout为例)
在介绍nextTick/Promise时,知道nextTick/Promise会在每个阶段后执行,这里以setTimeout为例说明。在setTimeout函数中会执行insert函数:
其中scheduleTimer(msecs)更新超时时间。
scheduleTimer函数执行流程
其中uv_timer_start函数就是更新超时时间(duration_ms)的(libuv采用最小二叉堆数据结构)。
在Event Loop 的timers阶段,判断是否超时,如果超时了执行RunTimers函数。
RunTimers函数定义在env.cc文件中
在RunTimers函数中会执行timers_callback_function函数,timers_callback_function函数是在node.js初始化的:
timers_callback_function函数就是processTimers函数。processTimers又调用listOnTimeout。
在listOnTimeout函数中,执行完一次setTimeout回调函数之后就要检查时候应该执行nextTick回调函数
第一次进入listOnTimeout函数时,因为ranAtLeastOneTimer为false,所以不会执行runNextTicks函数,执行完第一个setTimeout时,会进入runNextTicks函数。
setTimeout、setImmediate执行顺序
这段代码执行结果是不固定的
setTimeout和setImmediate都有可能在第一位。
在介绍setTimeout时,我们知道如果超时时间小于1或者大于TIMEOUT_MAX时,都会重置为1。所以当程序执行到uv_run(进入Event Loop时),这段时间经过多久是未知的,如果cpu不忙可能时间小于1,那么此时setTimeout回调函数不会执行,只能等待下次循环。那么setImmediate回调函数首先执行。如果cpu很忙,执行uv_run时,时间已经超过1毫秒,那么setTimeout回调函数执行。
参考资料
【nodejs原理&源码杂记(8)】Timer模块与基于二叉堆的定时器](https://www.cnblogs.com/huaweicloud/p/11861536.html)
nodejs 14.0.0源码分析之setTimeout
Node.js Event Loop 的理解 Timers,process.nextTick()
The text was updated successfully, but these errors were encountered: