Event Loop 事件循环简介

简介: Event Loop 事件循环简介

1、Event Loop?

Event Loop 其实也是在面试中经常会出现的一个题,前端程序员回答不上来是正常的,因为 Event Loop 是 C++ 实现的,实现原理和 JavaScript 没什么关系,我个人在之前的面试中遇到这个题的时候,也是看一些 C++ 程序员对 Event Loop 的分析,如果你想对 Event Loop 有一个深入的了解,那这里推荐你一篇 Event Loop 的官方文档,这有中文版:Event Loop。


在了解 Event Loop 之前我们可能需要一点计算机操作系统的知识,比如:当开发人员在电脑键盘上敲击了 a 键,那操作系统是如何知道的呢?大概是这样的,当 a 键盘被按下时,a 键的信息会通过键盘内的电路传递给操作系统,然后操作系统再通知浏览器,浏览器在得到 a 键的信息之后,就会将 a 显示到响应的位置。一句话,浏览器会接收到操作系统传递给它的事件。


那什么是 Event 事件?比如 setTimeout(fun1, delay) 在 delay 到时后需要执行的回调函数 fun1 就是事件,或者 fs.readFile("/1.txt', fun2) 文件读取后执行的 fun2 也是事件,再或者 http 网络请求相关的 server.on('close', fun3) ,在服务器关闭时执行的 fun3 也是事件。


那什么是 Loop 循环?Loop 就是循环的意思,比如我们学习编程过程中的 for、while 循环,它们是一个道理的。但是由于 Event 事件是分优先级的,所以在处理的时候也是要分先后顺序的,Node.js 就是按照顺序来轮询每种事件的。


Event Loop?回到上述所说的 Event 事件的问题,假如上述的场景,操作系统同时触发了三种事件,那 Node 会怎么办?答案是这几种事件有一个优先级分类,然后根据优先级排序执行,这就是 Event Loop 对事件处理顺序的管理。那这个顺序是怎样的呢?

2、Event Loop 顺序?

JavaScript 是单线程的,有了 event loop 的加持,Node.js 才可以非阻塞地执行 I/O 操作,把这些操作尽量转移给操作系统来执行。


浏览器使用了 V8 自带的 Event Loop 事件循环机制,它只有宏任务和微任务之间的循环,而 Node.js 是基于 libuv 自己做了一个 Event Loop,它的事件循环就相对比较复杂。首先看一下浏览器的事件循环:


JS 的任务队列分 macro-task(宏任务)与 micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

macro-task 包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。

micro-task 包括: process.nextTick, Promise., Object.observe(已废弃), MutationObserver(html5新特性)


Node.js 的事件循环分为六个阶段,如下图所示:其中每个方框都是 event loop 中的一个阶段。每个阶段都有一个 先入先出队列,这个队列存有要执行的回调函数(注:存的是函数地址)。不过每个阶段都有其特有的使命,一般来说,当 event loop 达到某个阶段时,会在这个阶段进行一些特殊的操作,然后执行这个阶段的队列里的所有回调。

   ┌───────────────────────────────────┐
┌─>│        timers 检查计时器的callback │
│  └──────────┬────────────────────────┘
│  ┌──────────┴────────────────────────┐
│  │     I/O callbacks                 │
│  └──────────┬────────────────────────┘
│  ┌──────────┴────────────────────────┐
│  │     idle, prepare                 │
│  └──────────┬────────────────────────┘      ┌───────────────┐
│  ┌──────────┴────────────────────────┐      │   incoming:   │
│  │         poll 轮询,检查系统事件     │<─────┤  connections, │
│  └──────────┬────────────────────────┘      │   data, etc.  │
│  ┌──────────┴────────────────────────┐      └───────────────┘
│  │      check 检查 setImmediate 回调  │
│  └──────────┬────────────────────────┘
│  ┌──────────┴────────────────────────┐
└──┤    close callbacks                │
   └───────────────────────────────────┘

timer 阶段:用于监测是否含有计时器的回调事件,如果有的话就执行,没有的话就进入下一阶段。


I/O callbacks:监测是否含有其他的回调,比如 TCP 报错,如果一个 TCP socket 开始连接时出现了 ECONNREFUSED 错误,一些 *nix 系统就会(向 Node.js)通知这个错误。这个通知就会被放入 I/O callbacks 队列。


idle,prepare:空闲阶段,一般用来清理一下内存相关,忽略不计。


poll:轮询,检查系统事件。这个阶段用来处理大部分事件,比如读文件,处理 http 请求等。


check:检查 setImmediate 回调,如果有的话就执行,没有的话就进入下一阶段。


close callbacks:执行关闭事件的回调函数,如 socket.on('close', fun) 里的 fun。


一个 Node.js 程序结束时,Node.js 会检查 event loop 是否在等待异步 I/O 操作结束,是否在等待计时器触发,如果没有,就会关掉 event loop。需要注意的是:大部分时间,Node.js 都停在 poll 轮询阶段,大部分事件都在 poll 阶段被处理,如文件、网络请求。

3、setImmediate() vs setTimeout()

setImmediate 和 setTimeout 很相似,但是其回调函数的调用时机却不一样。setImmediate() 的作用是在当前 poll 阶段结束后调用一个函数。setTimeout() 的作用是在一段时间后调用一个函数。这两者的回调的执行顺序取决于 setTimeout 和 setImmediate 被调用时的环境。


如果 setTimeout 和 setImmediate 都是在主模块(main module)中被调用的,那么回调的执行顺序取决于当前进程的性能,这个性能受其他应用程序进程的影响。举例来说,如果在主模块中运行下面的脚本,那么两个回调的执行顺序是无法判断的。但是,如果把上面代码放到 I/O 操作的回调里,setImmediate 的回调就总是优先于 setTimeout 的回调。

/* timeout_vs_immediate.js*/                      /* timeout_vs_immediate.js*/
setTimeout(() => {                                const fs = require('fs');
  console.log('timeout');                         fs.readFile(__filename, () => {
}, 0);                                                setTimeout(() => {
                                                          console.log('timeout');
setImmediate(() => {                                  }, 0);
  console.log('immediate');                           setImmediate(() => {
});                                                       console.log('immediate');
                                                      });
/*运行结果可能不同: */                             });
$ node timeout_vs_immediate.js                    //运行结果皆为:
timeout                                           $ node timeout_vs_immediate.js
immediate                                         immediate
                                                  timeout
$ node timeout_vs_immediate.js
immediate
timeout

setImmediate 的主要优势就是,如果在 I/O 操作的回调里,setImmediate 的回调总是比 setTimeout 的回调先执行。

目录
打赏
0
0
0
0
3
分享
相关文章
预期违背理论(expectancy violations theory)
预期违背理论(Expectancy Violations Theory)是由心理学家 John Bowlby 提出的,该理论认为人们在社交互动中会根据以往的经验和预期来判断他人的行为。当他人的行为与我们的预期相违背时,我们会产生一种心理上的不适感,这种不适感可能表现为惊讶、失望、愤怒等情绪。预期违背理论可以用来解释人们在社交互动中的情绪反应,以及为什么人们会对他人的行为产生不同的情感体验。
7026 4
VSCode下载与安装使用教程【超详细讲解】
VSCode下载与安装使用教程【超详细讲解】
4122 0
VSCode下载与安装使用教程【超详细讲解】
Qt线程池+生产者消费者模型
Qt线程池+生产者消费者模型
549 5
【Flume的大数据之旅】探索Flume如何成为大数据分析的得力助手,从日志收集到实时处理一网打尽!
【8月更文挑战第24天】Apache Flume是一款高效可靠的数据收集系统,专为Hadoop环境设计。它能在数据产生端与分析/存储端间搭建桥梁,适用于日志收集、数据集成、实时处理及数据备份等多种场景。通过监控不同来源的日志文件并将数据标准化后传输至Hadoop等平台,Flume支持了性能监控、数据分析等多种需求。此外,它还能与Apache Storm或Flink等实时处理框架集成,实现数据的即时分析。下面展示了一个简单的Flume配置示例,说明如何将日志数据导入HDFS进行存储。总之,Flume凭借其灵活性和强大的集成能力,在大数据处理流程中占据了重要地位。
226 3
51单片机实现俄罗斯方块游戏编程
设计了一款基于AT89C51单片机的俄罗斯方块游戏机,使用LCD12864液晶显示,按键控制方块移动与变形。游戏中,7种不同形状的方块随机下落,填满一行得分,满屏则游戏结束。包含电源、单片机最小系统、LCD和按键模块的硬件电路通过Proteus进行了仿真,展示了游戏运行、得分和计时等。代码部分展示了检查碰撞和更新地图的函数。
281 1
第三代软件开发-Popup弹窗
欢迎来到我们的 QML & C++ 项目!这个项目结合了 QML(Qt Meta-Object Language)和 C++ 的强大功能,旨在开发出色的用户界面和高性能的后端逻辑。 在项目中,我们利用 QML 的声明式语法和可视化设计能力创建出现代化的用户界面。通过直观的编码和可重用的组件,我们能够迅速开发出丰富多样的界面效果和动画效果。同时,我们利用 QML 强大的集成能力,轻松将 C++ 的底层逻辑和数据模型集成到前端界面中。 在后端方面,我们使用 C++ 编写高性能的算法、数据处理和计算逻辑。C++ 是一种强大的编程语言,能够提供卓越的性能和可扩展性。我们的团队致力于优化代码,减少资
【QML 创建界面】QML界面的动态创建及其其他方法 (Dynamic Creation of QML Interfaces and Other Methods)
【QML 创建界面】QML界面的动态创建及其其他方法 (Dynamic Creation of QML Interfaces and Other Methods)
1296 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问