深入解析node事件环原理

本文对比浏览器事件环详细解析Node.js事件循环机制,包括不同阶段的任务执行顺序与优先级,如timers、I/O callbacks、idle、poll、check、close callbacks等阶段的特点,以及微任务与宏任务的区别。

前言
在上一篇两张图,教你秒懂浏览器Event loop原理中我们详细的讲述了Event loop的基础概念及浏览器事件环的原理。本文中将对比浏览器事件环来说明下node的事件环原理。
如果你没有阅读过上一篇关于Event loop基础的说明,建议移步到上一篇文章中先阅读一下。

node事件环与浏览器事件环的区别

  1. node相比浏览器多了一个微任务process.next,且process.next执行的优先级比promise.then快
  2. 浏览器中异步任务只有微任务和宏任务两种优先级别,在node中宏任务也是有优先级别的,它的优先级由libuv内部的事件环机制决定。具体如下图:
    通过上图我们可以知道node中的异步任务大致分为几个执行阶段:
  • timers :是执行setTimeout、setInterval回调的,当定时器到时间后,会被放入放入timers对应的宏任务队列中;
  • I/O callbacks阶段:执行除了close事件的callbacks、被timers(定时器,setTimeout、setInterval等)设定的callbacks、setImmediate()设定的callbacks之外的callbacks;
  • idle, prepare 阶段: 仅node内部使用;
  • poll 阶段: 这个阶段是轮询时间,用于等待还未返回的 I/O 事件,比如服务器的回应、用户移动鼠标等等。
    这个阶段的时间会比较长。如果没有其他异步任务要处理(比如到期的定时器),会一直停留在这个阶段,等待 I/O 请求返回结果。
  • check 阶段: 执行setImmediate() 设定的callbacks;
  • close callbacks 阶段: 比如socket.on(‘close’,callback)的callback会在这个阶段执行.

上述的六个循环阶段,每一个循环阶段都对应这一个宏任务队列,用于存放当前阶段可执行的任务。
3.检查微任务队列的时机不一样,node是切换事件环阶段的时,即切换到下一个循环阶段时。浏览器是主线程执行栈被清空时。 4.在node中宏任务多了一个setImmediate,它属于六个轮询中的check阶段

poll阶段

poll阶段是衔接整个event loop各个阶段比较重要的阶段。
在node.js里,任何异步方法(除timer,close,setImmediate之外)完成时,都会将其callback加到poll queue里,并立即执行。
poll 阶段有两个主要的功能:

  1. 处理poll队列(poll quenue)的事件(callback);
  2. 执行timers的callback,当到达timers指定的时间时;

如果event loop进入了 poll阶段,且代码未设定timer,将会发生下面情况:

  • 如果poll queue不为空,event loop将同步的执行queue里的callback,直至queue为空,或执行的callback到达系统上限;
  • 如果poll queue为空,将会发生下面情况:
    • 如果代码已经被setImmediate()设定了callback, event loop将结束poll阶段进入check阶段,并执行check阶段的queue (check阶段的queue是 setImmediate设定的)
    • 如果代码没有设定setImmediate(callback),event loop将阻塞在该阶段等待callbacks加入poll queue;

如果event loop进入了 poll阶段,且代码设定了timer:

  • 如果poll queue进入空状态时(即poll 阶段为空闲状态),event loop将检查timers,如果有1个或多个timers时间时间已经到达,event loop将按循环顺序进入 timers 阶段,并执行timer queue.

代码示例

  • nextTick比then快示例:

    console.log('1');
    
    setTimeout(function () {
      console.log('2');
      process.nextTick(function () {
        console.log('3');
      })
      new Promise(function (resolve) {
        console.log('4');
        resolve();
      }).then(function () {
        console.log('5')
      })
    })
    new Promise(function (resolve) {
      console.log('7');
      resolve();
    }).then(function () {
      console.log('8')
    })
    process.nextTick(function () {
      console.log('6');
    })
    setTimeout(function () {
       console.log('9');
       process.nextTick(function () {
          console.log('10');
       })
    })
    复制代码

    执行结果:1、7、6、8、2、4、9、3、10、5
    执行的原理:

    1. 开始执行,首先将同步任务执行完成,输出1、7,同时将nextTick及then(输出8的then)放入微任务队列中、将setTimeout如果计时完成,则放入libuv的times阶段的队列
    2. 检查微任务队列,优先输出process.nextTick的6,然后输出then的8
    3. 进入libuv的times阶段,执行对应的宏任务队列,输出2,将nextTick(3)放入微任务队列,输出promise的4,将then(5)放入微任务队列,执行下一个setTimeout输出9,将nextTick(10)放入微任务队列
    4. 进入libuv的times阶段执行完成,检查为任务队列,优先输出3、10,然后输出5
  • setTimeout和setImmediate不一定谁先谁后示例:

      setTimeout(function () {
         console.log('setTimeout’);
      },0);
      
      setImmediate(function () {
         console.log('setImmediate’);
      });
    复制代码

    输出结果:不一定谁前谁后
    执行的原理:

    1. node执行需要准备的时间,如果准备的时间比定时器的时间长,当准备完成时,setTimeout已经被放入了执行timers(计时器)执行队列中。
    2. 当node的执行环境准备完成后,按照libuv的执行机制的顺序进行检查,先看timers,发现有任务则执行,没有则向下查找其他的阶段的对应队列,走到check(检查阶段的时候),setImmediate的回调事件已经在队列中了,则输出setImmediate,执行之后继续检查下一阶段的任务
    3. 如果在2中先检查到timers任务队列中有任务,则先输出setTimeout,如果第一次没检测到,则先输出的就是setImmediate

    小结:谁先输出是由node的准备时间和定时器设置的时间谁快来决定的,定时器快则先输出setTimeout,否则先输出setImmediate

  • poll的下一个阶段是check的示例:

    let fs = require('fs');
    fs.readFile('./1.txt', function () {
       setTimeout(() => {
          console.log('setTimeout')
       }, 0);
    
       setImmediate(() => {
          console.log('setImmediate')
       });
    });
    复制代码

    执行结果:setImmediate、setTimeout
    原理分析:
    因为fs的回调函数执行的阶段属于poll阶段,poll执行完,接下来的阶段就是check阶段,所以一定是setImmediate先输出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值