Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

事件循环:高性能到底是如何做到的 #8

Open
BYChoo opened this issue Mar 23, 2021 · 0 comments
Open

事件循环:高性能到底是如何做到的 #8

BYChoo opened this issue Mar 23, 2021 · 0 comments
Labels

Comments

@BYChoo
Copy link
Owner

BYChoo commented Mar 23, 2021

Node.js事件循环

事件循环同属来说就是一个无限的while循环

Node.js循环原理

可以看到,这一流程包含6个阶段,每个阶段代表的含义如下表示:

  1. timers: 本阶段已经被 setTimeout() 和 setInterval() 调度的回调函数,简单理解就是由这两个函数启动的回调函数
  2. pending callbacks: 本阶段执行某些系统操作 (如 TCP类型错误)的回调函数
  3. idle、prepare:仅系统内部使用,你只需要知道有这 2 个阶段就可以
  4. poll:检索新的 I/O 事件,执行与 I/O 相关的回调,其他情况 Node.js 将在适当的时候在此阻塞。这也是最复杂的一个阶段,所有的事件循环以及回调处理都在这个阶段执行,接下来会详细分析这个过程。
  5. check:setImmediate() 回调函数在这里执行,setImmediate 并不是立马执行,而是当事件循环 poll 中没有新的事件处理时就执行该部分,如下代码所示:
const fs = require('fs');
setTimeout(() => { // 新的事件循环的起点
    console.log('1'); 
}, 0);
setImmediate( () => {
    console.log('setImmediate 1');
});
/// 将会在 poll 阶段执行
fs.readFile('./test.conf', {encoding: 'utf-8'}, (err, data) => {
    if (err) throw err;
    console.log('read file success');
});
/// 该部分将会在首次事件循环中执行
Promise.resolve().then(()=>{
    console.log('poll callback');
});
// 首次事件循环执行
console.log('2');

在这一行代码中又一个非常奇特的地方,就是 setImmediate会在 setTimeout之后输出。有以下几点原因

  • setTimeout如果不设置时间活着设置时间为0,则会默认为1ms
  • 主流程执行完成后,超过1ms时,会将setTimeout回调函数鹿皮插入到代执行poll队列中;
  • 由于当前 pull队列 存在可执行回调函数,因此需要先执行完,待完全执行完成后,才会执行check:setImmediate。因此这也验证了这句话,先执行回调函数,再执行 setImmediate。
  • close callbacks:执行一些关闭的回调函数,如 socket.on('close', ...)。

运行七点

从图一中可以看出事件循环的七点是timers,如下代码所示:

setTimeout(() => {
    console.log('1');
}, 0);
console.log('2')

在代码 setTimeout 中的回调函数就是新一轮事件循环的起点,看到这里有很多同学会提出非常合理的疑问:“为什么会先输出 2 然后输出 1,不是说 timer 的回调函数是运行起点吗?”

这里有一个非常关键点,当 Node.js 启动后,会初始化事件循环,处理已提供的输入脚本,它可能会先调用一些异步的 API、调度定时器,或者 process.nextTick(),然后再开始处理事件循环。因此可以这样理解,Node.js 进程启动后,就发起了一个新的事件循环,也就是事件循环的起点。

总结来说,Node.js 事件循环的发起点有 4 个:

  • Node.js启动后
  • setTimeout回调函数
  • setInterval回调函数
  • 也可能是一次I/O后的回调函数

Node.js事件循环

在上面的核心流程中真正需要关注循环执行的就是 poll 这个过程。在poll过程中,主要处理的是异步 I/O 的回调函数,以及其他几乎所有的回调函数,异步I/O又分为网络I/O和文件I/O。这是我们常见的代码路基部分的异步回调逻辑

微任务和红任务

微任务: 在 node.js 中微任务包含2种 - process.nextTick 和 Promise。微任务在事件循环中优先级是最高的,因此在同一个事件循环中又其他任务存在时,优先执行微任务队列。并且process.nextTick和promise也存在优先级,process.nextTick高于Promise

宏任务: 在node.js中宏任务包含4种 - setTimeout、setInterval、setImmediate 和 I/O。宏任务在微任务执行之后执行,因此在统一事件循环周期内,如果既存在微任务队列又存在宏任务队列,那么优先将微任务队列清空,再执行宏任务队列。这也解析了我们前面提到的第3个问题,事件循环中的事件类型是存在优先级

@BYChoo BYChoo added the Node.js label Mar 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant