面试官:既然你知道浏览器的循环机制,那就讲一下node事件循环吧
浅聊一下
在之前的面试中经常会被问“事件循环机制”,依稀记得面试官问我:请你讲一讲node的事件循环机制,我虎躯一震,说:我不会~虽然已经过去很久,但还是来将node的事件循环机制弄明白...
在金三银四的尾巴上了车,如果你也和我一样也想在大三找一份还不错的实习,欢迎掘友们私聊我交流面经(wechat: LongLBond)
node 事件循环
在nodejs中,事件循环分为六个阶段,当Node启动时,会创建一个事件循环线程,并依次按照下图所示顺序进入每个阶段,执行每个阶段的回调。
- timers(定时器):此阶段执行那些由setTimeout()和setInterval()调度的回调函数
- /O callbacks(I/o回调):此阶段会执行几乎所有的回调函数,除了close callbacks(关闭回调)和那些由timers与setImmediate()调度的回调,
- idle(空转),prepare:此阶段只在内部使用
- polI(轮询):检索新的/O事件:在恰当的时候Node会阻塞在这个阶段
- check(检查):setImmediate()设置的回调会在此阶段被调用
- close callbacks(关闭事件的回调):诸如socket.on('close',·.)此类的回调在此阶段被调用
而我们主要要注意的就是timers poll check
三个阶段,先来看看执行顺序
-
如果event loop:进入了poll阶段,且代码未设定timer,将会发生下面情况:
1⃣️如果poll queue不为空,event loop将同步的执行queue里的callback,直至queue为空,或执行的callback到达系统上限
2⃣️如果poll queue为空,将会发生下面情况:
-
如果代码已经被setlmmediate()设定了callback,event loop:将结束poll阶段进入check阶段,并执行check阶段的queue(check阶段的queue是setlmmediate设定的)
-
如果代码没有设定setlmmediate(callback),event loop将阻塞在该阶段等待callbacks加入poll queue,一旦到达就立即执行
-
-
如果event loop进入了poll阶段,且代码设定了timer:
- 如果poll queue进入空状态时(即poll阶段为空闲状态),event loop将检查timers,.如果有1个或多个timers时间时间已经到达,event loop将按循环顺序进入timers阶段,并执行timer queue,
fs 和 setTimeout
写这么多太复杂了,直接通过例子来看。当 fs 遇上 setTimeout 的时候该如何执行?
var fs = require('fs');
var path = require('path');
function someAsyncOperation(callback) {
// 花费2毫秒
fs.readFile(path.resolve(__dirname, '/read.txt'), callback);
}
var timeoutScheduled = Date.now();
var fileReadTime = 0;
setTimeout(function () {
var delay = Date.now() - timeoutScheduled;
console.log('setTimeout:' + delay + "ms have passed since I was scheduled");
console.log('fileReaderTime', fileReadTime, timeoutScheduled);
}, 10);
someAsyncOperation(function () {
fileReadTime = Date.now();
while (Date.now() - fileReadTime < 20) {}
});
- 首先遇到setTimeout,假设它需要耗时10毫秒,此时它正在等待
- 然后遇到了fs,也就是I/O流操作,fs耗时2毫秒,在2毫秒以后,I/O流的回调函数加入到poll队列中,一直到22ms时,等待已久的setTimeout加入timer队列,此时检查到timer队列不为空,于是按顺序循环执行到timer(注意此时是第二次循环)
所以IO流先执行,setTimeout后执行
如果setTimeout耗时5ms,而fs耗时9ms呢?
那么经过5ms以后,setTimeout加入Timer队列,此时poll阶段发现timer队列不为空,于是先执行完timer队列,执行完以后,fs回调加入poll队列,继续将poll队列执行完毕
setTimeout 和 setImmediate
setTimeout和setImmediate到底谁先执行?
setTimeout(()=>{
console.log('juejue');
},0)
setImmediate(()=>{
console.log('immediate');
})
来看看结果:
诶,怎么有时候setTimeout先执行,有时候setImmediate先执行呢?
因为虽然setTimeout0ms后就执行,但是setTimeout启动也耗时,不一定0ms就能跑起来,所以无法确认他们俩到底谁先等待完毕
那我要如何保证他们的运行顺序呢?
fs.readFile(path.resolve(__dirname, '/read.txt'), ()=>{
setTimeout(()=>{
console.log('juejue');
},0)
setImmediate(()=>{
console.log('immediate');
})
});
- 先看timer阶段,暂时timer队列为空,进入poll阶段
- 存在fs,将fs的回调推入poll队列,执行poll队列
- setTimeout推入Timer队列,,setImmediate推入check队列,因为Timer队列不为空,于是按循环顺序先执行check队列,再执行Timer队列
process.nextTick
process.nextTick在什么时候调用?在阶段转换时调用,来举个例子
fs.readFile(path.resolve(__dirname, '/read.txt'), ()=>{
console.log('readFile');
setTimeout(()=>{
console.log('juejue');
},0)
setImmediate(()=>{
console.log('immediate');
})
process.nextTick(()=>{
console.log('nextTick');
})
});
这段代码十分的熟悉,刚才已经分析过了,唯一不同的就是在下面加上了一个process.nextTick
,当执行fs回调的时候,将setTimeout推入Timer队列,setImmediate推入check队列,然后因为Timer队列不为空,于是要顺着循环运行,此时要从poll阶段切换到check队列,process.next
立即调用,来看看结果
结尾
如果文章中有错误的地方,请大佬补充~~
转载自:https://juejin.cn/post/7376425155323772963