博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入解析node事件环原理
阅读量:6226 次
发布时间:2019-06-21

本文共 3325 字,大约阅读时间需要 11 分钟。

前言

在上一篇中我们详细的讲述了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先输出

转载地址:http://uqfna.baihongyu.com/

你可能感兴趣的文章
腾讯视频播放器V 1.0 去广告补丁
查看>>
实现本地上传的Kindetitor的Servlet版本
查看>>
Android学习笔记—第九章 Activity的加载模式
查看>>
C#设计模式系列:代理模式(Proxy)
查看>>
javaEE项目建立多个数据源并配置事务
查看>>
python-字符串格式化
查看>>
DNS配置笔记
查看>>
Chrome自定义最小字号
查看>>
Android多人视频聊天应用的开发(一)快速集成
查看>>
谷歌web站点安全扫描软件skipfish安装、配置、使用
查看>>
Centos 中如何快速定制二进制的内核 RPM 包
查看>>
zabbix 自动发现tomcat的war包并实现监控
查看>>
网络安全简介
查看>>
Nginx基本概念和安装
查看>>
我的友情链接
查看>>
10年以后第一次做作业的感受
查看>>
SpannableString与SpannableStringBuilder使用
查看>>
fastreport打印,设置
查看>>
如何批量导出已经开通Lync权限的用户
查看>>
C#4.0的dynamic和var及object关键字辨析
查看>>