十--nodejs原理(事件循环)

2023-11-12

一、事件循环
什么是事件循环?
事件循环使得nodejs可以通过将操作转移到系统内核中来执行非阻塞I/O操作(尽管javascripts是单线程的)。由于大多数现代内核是多线程的,因此它们可以处理在后台执行的多个操作。当这些操作之一完成时,内核会告诉nodejs,以便可以将适度的回调添加到轮询队列中以最终执行
通俗的说在nodejs内部使用了第三方库libuv,nodejs会把IO,文件读取等异步操作交由他处理,而nodejs主线程可以继续去处理其他的事情。libuv会开启不同的线程去处理这些延时操作,处理完后,会把异步操作的回调函数放到nodejs的轮询队列中,nodejs会在适当的时候处理轮询队列中的回调函数,从而实现非阻塞。
nodejs启动的时候,它将初始化事件循环,处理提供的输入脚本,这些脚本可能会进行异步api调用,调度计时器或调用process.nextTick,然后开始处理事件循环。
以下是事件循环模型
在这里插入图片描述
每个阶段都有一个要执行的回调 FIFO 队列。 尽管每个阶段都有其自己的特殊方式,但是通常,当事件循环进入给定阶段时,它将执行该阶段特定的任何操作,然后在该阶段的队列中执行回调,直到队列耗尽或执行回调的最大数量为止。 当队列已为空或达到回调限制时,事件循环将移至下一个阶段,依此类推。

  1. timers:此阶段执行由 setTimeout 和 setInterval 设置的回调。
  2. pending callbacks:执行推迟到下一个循环迭代的 I/O 回调。
  3. idle, prepare, :仅在内部使用。
  4. poll:取出新完成的 I/O 事件;执行与 I/O 相关的回调(除了关闭回调,计时器调度的回调和 setImmediate 之外,几乎所有这些回调) 适当时,node 将在此处阻塞。
  5. check:在这里调用 setImmediate 回调。
  6. close callbacks:一些关闭回调,例如 socket.on(‘close’, …)。

在每次事件循环运行之间,Node.js 会检查它是否正在等待任何异步 I/O 或 timers,如果没有,则将其干净地关闭。
1、timer阶段
计时器可以在回调后面指定时间阈值,但这不是我们希望其执行的确切时间。 计时器回调将在经过指定的时间后尽早运行。 但是,操作系统调度或其他回调的运行可能会延迟它们。-- 执行的实际时间不确定
例如:

const fs = require('fs');
function someAsyncOperation(callback){
  //假设它执行花了95ms
  fs.readFile('/path/to/file',callback);
}
const timeoutScheduled = Date.now();
setTimeout(()=>{
  const delay = Date.now()-timeoutScheduled;
  console.log(`${delay}ms have passed since i was scheduled`)
},100)
someAsyncOperation(()=>{
  const startCallback = Date.now();
  //这里在读取文件之后执行了一个10秒的循环
  //那么能执行定时器回调就一定是在105秒之后
  while(Date.now() - startCallback<10){
    //do nothing
  }
})

当事件循环进入 poll 阶段时,它有一个空队列(fs.readFile 尚未完成),因此它将等待直到达到最快的计时器 timer 阈值为止。
等待 95 ms 过去时,fs.readFile 完成读取文件,并将需要 10ms 完成的其回调添加到轮询 (poll) 队列并执行。
回调完成后,队列中不再有回调,此时事件循环已达到最早计时器 (timer) 的阈值 (100ms),然后返回到计时器 (timer) 阶段以执行计时器的回调。
在此示例中,您将看到计划的计时器与执行的回调之间的总延迟为 105ms。
2、pending callback
此阶段执行某些系统操作的回调,例如 TCP 错误。 平时无需关注
3、轮询poll阶段
主要有两个功能:
1)、计算应该阻塞并I/O轮询的时间
2)、处理轮训队列中的(poll queue)中的事件
当事件循环进入轮询 (poll) 阶段并且没有任何计时器调度 (timers scheduled) 时,将发生以下两种情况之一:
1)如果轮询队列 (poll queue) 不为空,则事件循环将遍历其回调队列,使其同步执行,直到队列用尽或达到与系统相关的硬限制为止 (到底是哪些硬限制?)。
2)如果轮询队列为空,则会发生以下两种情况之一:
2.1 如果已通过 setImmediate 调度了脚本,则事件循环将结束轮询 poll 阶段,并继续执行 check 阶段以执行那些调度的脚本。
2.2 如果脚本并没有 setImmediate 设置回调,则事件循环将等待 poll 队列中的回调,然后立即执行它们。
一旦轮询队列 (poll queue) 为空,事件循环将检查哪些计时器 timer 已经到时间。 如果一个或多个计时器 timer 准备就绪,则事件循环马上结束这一阶段往下一阶段执行,直到返回到计时器阶段,以执行这些计时器的回调。
4、检查阶段check
此阶段允许在轮询 poll 阶段完成后立即执行回调。 如果轮询 poll 阶段处于空闲,并且脚本已使用 setImmediate 进入 check 队列,则事件循环可能会进入 check 阶段,而不是在 poll 阶段等待。
setImmediate 实际上是一个特殊的计时器,它在事件循环的单独阶段运行。 它使用 libuv API,该 API 计划在轮询阶段完成后执行回调。
通常,在执行代码时,事件循环最终将到达轮询 poll 阶段,在该阶段它将等待传入的连接,请求等。但是,如果已使用 setImmediate 设置回调并且轮询阶段变为空闲,则它将将结束并进入 check 阶段,而不是等待轮询事件。
5、close callbacks 阶段
如果套接字或句柄突然关闭(例如 socket.destroy),则在此阶段将发出 ‘close’ 事件。 否则它将通过 process.nextTick 发出。
二、事件循环相关问题
1、setImmediate 和 setTimeout 的区别
setImmediate 和 setTimeout 相似,但是根据调用时间的不同,它们的行为也不同。
1)setImmediate设计为当前poll阶段完成后执行脚本
2)setTimeout计划在以毫秒为单位的最小阈值过去之后执行脚本
Tips: 计时器的执行顺序将根据调用它们的上下文而有所不同。 如果两者都是主模块中调用的,则时序将受到进程性能的限制。
(1)在主模块中执行----宏任务代码
两者的执行顺序是不固定的, 可能timeout在前, 也可能immediate在前。

setTimeout(()=>{
  console.log('timeout');
},0)
setImmediate(()=>{
  console.log('immediate')
})

(2)在同一个I/O回调里执行,或timers回调里
setImmediate总是先执行

const fs = require('fs');
fs.readFile(__filename,()=>{
  setTimeout(()=>{
    console.log('timeout');
  },0)
  setImmediate(()=>{
    console.log('immediate')
  })
})

那为什么在外部 (比如主代码部分 mainline) 这两者的执行顺序不确定呢?
因为在 主代码 部分执行 setTimeout 设置定时器 (此时还没有写入队列),与 setImmediate 写入 check 队列。
mainline 执行完开始事件循环,第一阶段是 timers,这时候 timers 队列可能为空,也可能有回调;
如果没有那么执行 check 队列的回调,下一轮循环在检查并执行 timers 队列的回调;
如果有就先执行 timers 的回调,再执行 check 阶段的回调。因此这是 timers 的不确定性导致的。
那么在timer回调里执行settimeout和setimmediate,setimmediate先执行。一样的道理,在timer回调里执行代码,定时器不会马上写入队列。那么timer回调执行完下一个阶段就是poll和check阶段,先执行immedaite。

setTimeout(()=>{
  setTimeout(() => {
  console.log('timeout');
  }, 0);
  setImmediate(() => {
  console.log('immediate');
  });
  new Promise(function (resolve) {
        console.log('promise1')
        resolve();
        console.log('promise2')
    }).then(function () {
        console.log('promise3')
    })
},0)
setTimeout(()=>{
  new Promise(function (resolve) {
    console.log('promise11')
    resolve();
    console.log('promise21')
  }).then(function () {
      console.log('promise31')
  })
  setTimeout(() => {
    console.log('timeout11');
    }, 0);
},0)

我们看以上代码的输出是:
promise1
promise2
promise3
promise11
promise21
promise31
immediate
timeout
timeout11
我的理解promise11比pomise3还迟输出,那意味着第一个settimeout队列回调完成先执行自己的微任务。在主线结束后以及事件循环的每个阶段之后,立即运行微任务回调。所以timer一个回调结束后会执行微任务回调。
注意
node10版本以前:
1)、执行完一个阶段的所有任务;
2)、执行完nextick队列里面的内容;
3)、然后执行微任务队列里的内容。
node11之后:
1)和浏览器统一,都是每执行完一个宏任务就去执行微任务队列。
所以这也能解释以上promise11在promise3之后输出,且immediate在最后输出。顺序是timer阶段的一个宏任务执行完就去执行微任务,同一时刻timer有两个宏任务,上一个宏任务的微任务执行完继续执行宏任务,接着执行微任务,执行完timer的回调队列为空就去执行下一阶段的check阶段即immediate,然后在执行后面的宏任务。
2、process.nextTick
process.nextTick 从技术上讲不是事件循环的一部分。 相反,无论事件循环的当前阶段如何,都将在当前操作完成之后处理 nextTickQueue。
在这里插入图片描述
3、process.nextTick 和 setImmediate 的区别
1)process.nextTick 在同一阶段立即触发
2)setImmediate fires on the following iteration or ‘tick’ of the event loop (在事件循环接下来的阶段迭代中执行 - check 阶段)。
注意因为process.nextTick会在事件轮询每个阶段之间执行, 如果递归调用nextTick, 就会导致轮询阻塞,所以尽量避免使用process.nextTick, 可以使用setImmediate代替。
4、microtasks微任务
在 Node 领域,微任务是来自以下对象的回调:

  1. process.nextTick()
  2. then()
    主线结束后以及事件循环的每个阶段之后,立即运行微任务回调。
    resolved 的 promise.then 回调像微任务处理一样执行是放入microTaskQueue队列中,就像 process.nextTick 一样。 虽然,如果两者都在同一个微任务队列中,则将首先执行 process.nextTick 的回调。
    优先级 process.nextTick > promise.then
    5、代码执行
async function async1() {
    console.log('async1 start')
    await async2()//这个就相当于是async2().then(()=>{console.log('async1 end')}
    console.log('async1 end')//所以这个被放入微任务里
}
async function async2() {
    console.log('async2')
}
console.log('script start')
setTimeout(function () {
    console.log('setTimeout0')
    setTimeout(function () {
        console.log('setTimeout1');
    }, 0);
    setImmediate(() => console.log('setImmediate'));
}, 0)

process.nextTick(() => console.log('nextTick'));//在同一轮微任务中,它优先级最高
async1();
new Promise(function (resolve) {
    console.log('promise1')
    resolve();
    console.log('promise2')
}).then(function () {
    console.log('promise3')//这个被放入微任务里
})
console.log('script end')

输出顺序:
script start
async1 start
async2
promise1
promise2
script end
//开始微任务回调
nextTick
async1 end
promise3
//settimeout timer回调
setTimeout0
//同一个i/o和timers里setImmediate比setTimeout先执行
setImmediate
setTimeout1

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

十--nodejs原理(事件循环) 的相关文章

  • 检查用户是否从后端 Firebase 身份验证登录

    在我的前端 用户使用 Firebase 身份验证浏览器登录 那部分工作得很好 除了 Firebase 后端之外 我还有提供其他内容的 NodeJS 后端 但是 我需要仅向经过身份验证的用户提供内容 我的问题是 有没有办法让我的 NodeJS
  • 如何使用 Passport /Facebook 策略/验证 Supertest 请求?

    我使用 Passport js 进行身份验证 Facebook 策略 并使用 Mocha 和 Supertest 进行测试 如何使用 Supertest for Facebook 策略创建会话并发出经过身份验证的请求 这是用户未登录时的示例
  • PDF 附件 NodeMailer

    预先感谢您的回复 我编写了一些使用nodemailer 0 7 1的代码 它发送电子邮件并将pdf附加到电子邮件中 但是 pdf 附件在编码时会自行损坏或截断或发生其他情况 我之所以说这是附件之前的文件 即我本地的文件 是512kb 而电子
  • 任何 JavaScript 代码都是有效的 TypeScript 代码吗?

    目前我已经开始学习TypeScript 从我研究过的文档来看TypeScript 我看到一些纯的样品JavaScript代码可以编译为TypeScript code 我的问题是 TypeScript 语言的设计方式是否使任何 JavaScr
  • .then(functionReference) 和 .then(function(value){return functionReference(value)}) 之间有区别吗?

    给定一个用于处理的命名函数Promise value function handlePromise data do stuff with data return data a 传递命名函数handlePromise作为参考 then pro
  • 如何从矩形点计算旋转角度?

    我有4分1 2 3 4闭合一个矩形 这些点按以下方式排列在数组中 x1 y1 x2 y2 x3 y3 x4 y4 我遇到的问题是矩形可以旋转一定角度 如何计算原始点 灰色轮廓 和角度 我试图在 javascript css3 transfo
  • 如何为多个元素添加Class?

    我正在使用这段 javascript 向多个元素添加一个类 我试图引用多个 div 并向它们添加类 它只适用于第一个 JavaScript
  • Angularjs $http 似乎不理解响应中的“Set-Cookie”

    我有一个带有 Passport 模块的 Nodejs Express REST api 用于身份验证 登录方法 GET 在标头中返回一个 cookie 当我从 Chrome 调用它时 它工作正常 我的 cookie 已在我的浏览器中设置 但
  • 为什么 if 语句中的赋值等于 true?

    首先我要说的是我理解两者之间的区别 and 第一个用于将右侧值分配给左侧变量 第二个用于比较两个值的等价性 第三个不仅用于等价性 还用于类型比较 即true 1会回来false 所以我知道almost任何时候你看到if 作者很有可能打算使用
  • 如何在 Vue.js 2 中使用事件总线通过自定义事件传递数据

    我在用着Vue js 2 5 x 在我的玩具项目中 我实现了一个事件总线 类似于所示的here https alligator io vuejs global event bus 事件总线在 Vue 原型中全局注册为 eventBus 然后
  • c3js数据标签的位置

    有没有可能的方法来更改数据上方标签的位置c3条形图 在官方文档中 很好地解释了如何通过操作 y 和 x 整数来更改 x 和 y 测量轴上标签的位置 但我没有找到任何数据标签 我试图用简单的方式指出它d3其上c3是基于但是console lo
  • 添加元数据到快速路线

    有什么方法可以将元数据添加到 Express 的路线中吗 例如 app get some route function req res some meta data 我正在寻找一种针对我的节点应用程序的 AOP 方法 因此我想通过身份验证和
  • jQuery.ajax() 记录 HTTP 请求

    我有一个发送 HTTP POST 请求的函数 我想记录它以进行调试 这是函数 function serverRequest URL DATA callback ajax url URL type POST dataType text con
  • IE 开发工具断点不起作用

    我正在尝试在 IE 11 中调试一些 javascript 但无法强制它在断点处停止 debugger 行工作正常 停止该行中的调试器 相同的文件没有debugger 行但在同一位置设置断点不会执行任何操作 功能正常 但调试器不会在断点处停
  • 如何清除画布中图像上的矩形

    我需要清除画布中图像上绘制的矩形 而不损坏现有图像 我可以绘制小矩形点并将其清除 但问题是 当我清除矩形时 它在图像上仍保留为白色小斑点 有人可以告诉我如何清除图像上的矩形而不损坏现有图像 我使用了以下方法来清除矩形 但没有用 1 cont
  • 插入四个空格而不是制表符

    我试图在按下 Tab 键时插入四个空格 我正在使用以下代码 请参阅spaces t 但是当我将其切换到spaces 当我按 Tab 时只插入一个空格 我还尝试了 function textarea keydown function e va
  • 使用来自Processing-JS的JSON

    我想使用编写一个应用程序处理 JS http processingjs org 并且我希望能够使用服务器端数据加载它 我还没有编写服务器端 所以我可以使用任何东西 但似乎明显的 AJAX 事情是使用 JSON 将数据上传到页面中 如何从我的
  • 为什么转换 new.Date() .toISOString() 会改变时间?

    我正在以两种不同的格式在数据库中插入日期 这是作为日期时间插入 var mydate mydate new Date document getElementById clockinhour value mydate toISOString
  • 使用 Google Visualization,为什么 DataView 内容显示在 ChartRangeFilter 中,而不显示在其关联的 LineChart 中?

    下面的代码应该从 CSV 文件填充 DataView 然后 DataView 被输入到 DashBoard 其中包含绑定在一起的 LineChart 和 ChartRangeFilter 我的问题是 虽然 ChartRangeFilter
  • Page_ClientValidate 正在验证多次。

    我的问题是 验证摘要消息 警报 显示两次 我无法弄清楚原因 请帮忙 这是代码 function validate javascript function if typeof Page ClientValidate function var

随机推荐

  • 工作中经常使用shell脚本

    在工作中我们常用shell脚本处理一些问题 今天在来一些这里整理了一些工作中常用的简单shell脚本 1 更新脚本 bin bash apt get update DEBIAN FRONTEND noninteractive apt get
  • 【C语言】小游戏-扫雷(清屏+递归展开+标记)

    大家好 我是深鱼 目录 一 游戏介绍 二 文件分装 三 代码实现步骤 1 制作简易游戏菜单 2 初始化棋盘 11 11 3 打印棋盘 9 9 4 布置雷 5 计算 x y 周围8个坐标的和 6 排查雷 lt 1 gt 清屏后打印棋盘 lt
  • Python:赋值,浅拷贝(copy)和深拷贝(deepcopy)

    基础知识请查看之前博客 Python 对象 可变对象与不可变对象 赋值 浅拷贝和深拷贝的关键问题 修改一个变量 会不会导致另外拷贝出来的对象的改变 不可变对象 import copy a1 0 a2 a1 a3 copy copy a1 a
  • 使用https://mail.google.com/登录GMail

    原来使用gmail google com登录 登录可以进去 但查看邮件时 总是出现 Oop unable to reach Gmail Please check your internet connection and try again
  • spring-boot后端解决跨域问题

    代码 import cn hutool log Log import cn hutool log LogFactory import com alibaba fastjson JSONObject import org springfram
  • 添加静态路由实现不同网段的路由的通信和不用网段之间设备的通信

    两不同网段的路由器 如何互通 三个案例详解 gzmenghai com
  • 下一代电信城域网设计原则

    下一代电信城域网设计原则 作者 epon 运营商早期建设的IP城域网多采用大L3 小L3的组网模式 核心层旁挂BAS 在运营中遇到很多问题 过大的二层网络 导致网络的安全性 可靠性较差 网络不可管理 传统L3设备 采用低成本ASIC套片 提
  • error:expected '=',',',';','asm'or'_attribute_'

    今天在Linux上调一个存包队列 当用gcc编译时 出现error expected asm or attribute 等错误 这个错误是出现在两个函数上 这两个函数的返回类型是bool 当我把bool类型改为void 再进行编译时 错误就
  • 菜鸟教程《Python 3 教程》笔记(18):File(文件)方法

    菜鸟教程 Python 3 教程 笔记 18 18 File 文件 方法 18 1 open 方法 18 2 file 对象 18 2 1 flush 18 2 2 fileno 18 2 3 isatty 18 2 4 truncate
  • PROFINET趣谈——设备模型

    设备名 MAC地址和IP地址是为了在网络中找到对应设备 而要定位确切的输入 IX1 1 输出 QW2 就需要熟悉设备模型的概念 PROFINET IO的设备类型与PROFIBUS几乎相同 如图所示 设备模型包括若干槽 slot 与子槽 su
  • Java内存泄露监控工具:JVM监控工具介绍

    jstack 如果java程序崩溃生成core文件 jstack工具可以用来获得core文件的java stack和native stack的信息 从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题 另外 jstack工具还可以附
  • BUAA词频统计(树实现)

    问题描述 编写程序统计一个英文文本文件中每个单词的出现次数 词频统计 并将统计结果按单词字典序输出到屏幕上 要求 程序应用二叉排序树 BST 来存储和统计读入的单词 注 在此单词为仅由字母组成的字符序列 包含大写字母的单词应将大写字母转换为
  • Linux 解决vi键盘方向键出现字母的问题

    修改 etc vim vimrc tiny 1 将 set compatible 兼容模式 改成 set nocompatible 非兼容模式 2 添加 set backspace 2 解决退格键无法使用
  • 【完全开源】小安派-Cam-D 摄像头核心板

    文章目录 一 概述 二 系统框图 三 摄像头电路 四 内存卡电路 五 IO引脚说明 六 资料 一 概述 小安派 Cam D AiPi Cam D 是安信可科技为高性能模组Ai M61 32S设计的一款摄像头核心板 引脚完全兼容Ai WB1
  • MFC :CCoolBar 的替代方案 CDockablePane。

    阅读受众需有一定MFC知识储备 技术支持 http www cnblogs com shuhaoc archive 2011 06 26 cdockableform html 在以往很多使用CCoolBar实现窗口停靠功能 但是在VS201
  • 【C++】Modbus通讯

    C Modbus通讯 2016年06月22日 20 37 48 Taily老段 阅读数 10298 版权声明 本文为博主原创文章 未经博主允许不得转载 如遇到疑问 评论会给出答复 学习交流 关注页面微信公众号 吃良心 拉思想 https b
  • R语言入门教程知识 第七章 特殊值

    以下为本章所用代码 letters letters 5 9 LETTERS LETTERS 6 10 month name month name 7 11 month abb month abb 8 12 pi NA length vec
  • 手撕self-attention代码_从0实现self-attention_附学习路线

    一 前言 科研需要 前几天自学了transformer 在理解self attention时 发现网上并没有一套成熟易懂的学习路线 对新手及其不友好 大多数教程只重视理论和公式的讲解 没有从零开始的代码实战 因此 我在这里整理了一条最适合新
  • python爬虫实战之最简单的网页爬虫教程

    在我们日常上网浏览网页的时候 经常会看到一些好看的图片 我们就希望把这些图片保存下载 或者用户用来做桌面壁纸 或者用来做设计的素材 下面这篇文章就来给大家介绍了关于利用python实现最简单的网页爬虫的相关资料 前言 网络爬虫 又被称为网页
  • 十--nodejs原理(事件循环)

    一 事件循环 什么是事件循环 事件循环使得nodejs可以通过将操作转移到系统内核中来执行非阻塞I O操作 尽管javascripts是单线程的 由于大多数现代内核是多线程的 因此它们可以处理在后台执行的多个操作 当这些操作之一完成时 内核