• NodeJs的Event Loop


    我们之前谈过浏览器的Event Loop:https://www.cnblogs.com/amiezhang/p/11349450.html

    简单来说,就是每执行一个宏任务,就去执行微任务队列,直到清空,再执行下个宏任务。

    那么NodeJs的Event Loop是怎么样的呢?

    NodeJS的Event Loop

    NodeJs的Event Loop其实也分了宏任务和微任务

    不同的是,不是每执行一个宏任务就回去清空一次微任务队列,可能是连着执行好几个才去清空一次微任务队列。

    例子:

    setTimeout(()=>{
        console.log(1);
        new Promise(resolve=>resolve()).then(()=>console.log(3))
    })
    setTimeout(()=>{console.log(2)})

    同一个例子,在浏览器和Node环境下,输出不一致。

    浏览器:

    NodeJs:

    可以看出:

    浏览器在执行第一个 setTimeout 宏任务时,往微任务队列推了一个 console.log(3 ),所以在执行完第一个setTimeout后,浏览器的 Event Loop 机制马上去执行微任务队列,然后再执行下个setTimeout宏任务

    NodeJs 在执行第一个 setTimeout 宏任务时,也往微任务队列推了一个 console.log(3),但是NodeJs的 Event Loop 机制却继续去执行下个 setTimeout 宏任务,等2个 setTimeout 宏任务都执行完后,才去执行微任务队列。

    宏任务和微任务的执行机制图

    图中 Tick 就是执行清空微任务的时机,我们可以看到 NodeJs 的 Event Loop 分为 5 个阶段,在每个阶段间会执行清空一次微任务队列。

    所以不难理解,在上面的例子,Times 阶段有 2 个宏任务,所以,console.log(2) 需要等整个 Times 阶段走完,才能得以执行。

     

    NodeJs 各个执行阶段

    推荐阅读:https://segmentfault.com/a/1190000013102056

    timers

    一个timer指定一个下限时间而不是准确时间,在达到这个下限时间后执行回调。在指定的时间过后,timers会尽早的执行回调,但是系统调度或者其他回调的执行可能会延迟它们。

    从技术上来说,poll阶段控制timers什么时候执行,而执行的具体位置在timers

    下限的时间有一个范围:[1, 2147483647],如果设定的时间不在这个范围,将被设置为1。

    I/O callbacks

    这个阶段执行一些系统操作的回调,比如说TCP连接发生错误。

    idle, prepare

    系统内部的一些调用。

    poll

    这是最复杂的一个阶段。

    poll阶段有两个主要的功能:一是执行下限时间已经达到的timers的回调,一是处理poll队列里的事件

    注:Node很多API都是基于事件订阅完成的,这些API的回调应该都在poll阶段完成。

    笔者把官网陈述的情况以不同的条件分解,更加的清楚。(如果有误,师请改正。)

    当事件循环进入poll阶段:

    • poll队列不为空的时候,事件循环肯定是先遍历队列并同步执行回调,直到队列清空或执行回调数达到系统上限。
    • poll队列为空的时候,这里有两种情况。

      • 如果代码已经被setImmediate()设定了回调,那么事件循环直接结束poll阶段进入check阶段来执行check队列里的回调。
      • 如果代码没有被设定setImmediate()设定回调:

        • 如果有被设定的timers,那么此时事件循环会检查timers,如果有一个或多个timers下限时间已经到达,那么事件循环将绕回timers阶段,并执行timers的有效回调队列。
        • 如果没有被设定timers,这个时候事件循环是阻塞在poll阶段等待回调被加入poll队列。

    check

    这个阶段允许在poll阶段结束后立即执行回调。如果poll阶段空闲,并且有被setImmediate()设定的回调,那么事件循环直接跳到check执行而不是阻塞在poll阶段等待回调被加入。

    setImmediate()实际上是一个特殊的timer,跑在事件循环中的一个独立的阶段。它使用libuvAPI来设定在poll阶段结束后立即执行回调。

    注:setImmediate()具有最高优先级,只要poll队列为空,代码被setImmediate(),无论是否有timers达到下限时间,setImmediate()的代码都先执行。

    close callbacks

    如果一个sockethandle被突然关掉(比如socket.destroy()),close事件将在这个阶段被触发,否则将通过process.nextTick()触发。

    关于setTimeout和setImmediate

    网上很多人说,这样的代码,会有随机性(自己尝试一直setTimeout先执行,偶现一次setImmediate先执行):

    setTimeout(() => {
        console.log('setTimeout');
    }, 0); // 当 delay 设置为 0 的时候,NodeJs 会设置为 1ms,因为最小是 1ms
    setImmediate(() => {
        console.log('setImmediate');
    })

    为什么呢?

    我们在上面看到在 poll 阶段,如果执行队列为空,它就会去询问 timers 那是否已经有“准备好“(例如超过 delay 时限的setTimeout)的回调函数。
    如果有就先到 timers 阶段去执行setTimeout;如果没有,则去询问 check 那是否有 setImmediate 注册回调,有就直接调用setImmediate。

    因为 setImmediate 是不用等就可以马上注册好的,所以随机性就在 setTimeout。

    因为 setTimeout 要等 1ms 才能”准备好“,所以执行到 poll 的时候,如果 setTimeout 的 delay(这里是 1ms) 过去了,那么 setTimeout 会先执行。

    如果执行到 poll 的时候,1ms 都还没过去,那么 setTimeout 就相当于没有准备好, 这样 setImmediate 就先执行了。

  • 相关阅读:
    访问日志不记录静态文件、访问日志切割、静态元素(文件)过期时间
    apache用户认证、域名跳转、Apache访问日志(两种格式)
    apache 和 php 整合、apache配制虚拟机
    PHP5和PHP7的安装、PHP和apache的整合!
    mariaDB的安装 apache的安装
    lamp 相关mysql的安装
    rsync命令 续集 、linux系统日志、screen工具
    rsync命令
    linux cron计划任务、chkconfig 命令、systemd命令、unit 相关、target 相关
    spark_to_es
  • 原文地址:https://www.cnblogs.com/amiezhang/p/11380943.html
Copyright © 2020-2023  润新知