likes
comments
collection
share

面试官:既然你知道浏览器的循环机制,那就讲一下node事件循环吧

作者站长头像
站长
· 阅读数 51

浅聊一下

在之前的面试中经常会被问“事件循环机制”,依稀记得面试官问我:请你讲一讲node的事件循环机制,我虎躯一震,说:我不会~虽然已经过去很久,但还是来将node的事件循环机制弄明白...

在金三银四的尾巴上了车,如果你也和我一样也想在大三找一份还不错的实习,欢迎掘友们私聊我交流面经(wechat: LongLBond

node 事件循环

在nodejs中,事件循环分为六个阶段,当Node启动时,会创建一个事件循环线程,并依次按照下图所示顺序进入每个阶段,执行每个阶段的回调。

面试官:既然你知道浏览器的循环机制,那就讲一下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三个阶段,先来看看执行顺序

  1. 如果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,一旦到达就立即执行

  2. 如果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) {}
});
  1. 首先遇到setTimeout,假设它需要耗时10毫秒,此时它正在等待

面试官:既然你知道浏览器的循环机制,那就讲一下node事件循环吧

  1. 然后遇到了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');
})

来看看结果:

面试官:既然你知道浏览器的循环机制,那就讲一下node事件循环吧

诶,怎么有时候setTimeout先执行,有时候setImmediate先执行呢?

因为虽然setTimeout0ms后就执行,但是setTimeout启动也耗时,不一定0ms就能跑起来,所以无法确认他们俩到底谁先等待完毕

那我要如何保证他们的运行顺序呢?

fs.readFile(path.resolve(__dirname, '/read.txt'), ()=>{
    setTimeout(()=>{
        console.log('juejue');
    },0)
    
    setImmediate(()=>{
        console.log('immediate');
    })
});
  1. 先看timer阶段,暂时timer队列为空,进入poll阶段
  2. 存在fs,将fs的回调推入poll队列,执行poll队列
  3. 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立即调用,来看看结果

面试官:既然你知道浏览器的循环机制,那就讲一下node事件循环吧

结尾

如果文章中有错误的地方,请大佬补充~~

转载自:https://juejin.cn/post/7376425155323772963
评论
请登录