初学node之事件循环

22 阅读2分钟

node事件循环

libuv

我们知道浏览器中的事件循环,异步任务是通过多线程调度由渲染主线程来完成的

而node底层用libuv来处理不同的异步任务,当收到请求或者文件读写的操作时,会让libuv去处理,libuv就会在线程池中调度空闲的线程去执行。

消息队列

与浏览器的事件循环一样,起初都是先执行同步代码,当同步代码执行完毕后,会查看消息队列中是否有待执行的任务。如果没有待执行的任务,主线程就会进入休眠状态。

这时还不算事件循环,事件循环是由libuv发起了异步任务,当到达可执行状态时,会被加入到不同的消息队列(宏任务)等待主线程去领取任务。

消息队列:

  • 微任务
    • process.nextTick、promise.then()、queueMicroTask
  • 宏任务有8个队列
    • timer(setTimeout、setInterval)
    • pending callbacks阶段
    • idle/prepare
    • poll轮训( 网络请求、读写文件、IO操作)
    • check(setImmediate)
    • close callbacks(soket.on(’close’, …))

宏任务队列的执行顺序从上到下,每次必须把这个阶段的所有任务都执行完后,才会进入下一个阶段。比较值得注意的是:

timer队列:

用来处理定时器结束时的回调,事件循环时必须把每个阶段的任务都处理完尘才会进入下一个阶段,导致定时器回调任务被执行的时机肯定是靠后的,具体执行的时机无法预测

poll轮训队列

在处理完本阶段的所有任务后,如果后面阶段(check或timer)没有已经ready的任务,则一直等待,这个设计的目的是为了当网络请求或者IO操作时可以第一时间进行处理。

check队列

用来处理setImmediate定时器的回调,由于在poll轮训后面,所以比timer队列的执行效率高

event loop

  1. 执行同步代码,执行完毕后
  2. 优先查看消息队列中微队列是否有任务
  3. 清空所有微队列的任务,并优先执行process.nextTick
  4. 按照上面的阶段,先查看timer消息队列,将该队列的任务全部执行完毕后
  5. 查看微队列中是否有待执行的任务
    1. 依此执行所有微队列任务
    2. 没有继续下一个阶段
  6. 进入下一个pending callbacks队列
    1. 重复【5】
  7. 进入poll队列,执行所有任务后,
    1. 查看后续队列是否有ready的任务,如果没有则等待
    2. 如果有其他阶段的任务则继续下一个队列,执行下一个队列之前执行【5】
  8. 循环执行所有宏任务队列

执行流程图

image.png