nodejs事件循环

158 阅读4分钟

事件循环

资料链接 [www.builder.io/blog/visual…]

先简单概述:对于没有基础的,对事件循环这个词都会感到抽象, 循环就会形成一个圆 你可以画一个圆 ,分成六份,每一份就是一个事件循环队列 然后用手指,在你的这个圆上开始照着轮廓不停的转动 。 只要是手指经过的区域,就会从相应队列中取出要执行的操作,没有就继续转。 在node中:node开始执行 就自动开始转 直到 没有更多待处理的工作时(即没有更多的计时器、没有更多的待处理 I/O 或异步操作),事件循环可能会结束。当然这不代表着node就结束了。 Node.js 进程的退出:当事件循环中没有更多待处理的工作,且没有额外的工作被添加到事件循环中时,Node.js 进程将退出; 在process中有一个exit方法 可以写个代码 自己退出node进程

node runtime

  • node 所需依赖 V8 | libuv | crypto
  • 提供文件系统访问和网络等功能的 c++特性
  • 建立在 c++上 js 库,旨在通过 js 语言调用 c 简单提一下:nodejs执行-->找底层c--->uinx内核--->系统交互 操作 memory storage network网卡 什么了

libuv

其中 libuv 是异步编程的关键 每当我们执行异步方法时,libuv 就会接管任务的执行。然后 Libuv 使用操作系统的本机异步机制运行该任务。如果本机机制不可用或不充分,它会利用其线程池来运行任务,确保主线程不会被阻塞。 libuv 有助于处理 Node.js 中的异步操作。对于处理网络请求等异步操作,libuv 依赖于操作系统原语。对于异步操作(例如读取没有本机操作系统支持的文件),libuv 依赖其线程池来确保主线程不被阻塞。然而,这确实引发了一些问题。

事件循环队列

  • 计时器队列
  • I/O 队列 :例如与 fs 和 http 模块关联的方法。 & pooling I/O 实现非阻塞 I/O操作
  • 检查队列 :保存与 setImmediate 函数相关的回调
  • 关闭队列 :异步任务的关闭事件相关的回调
  • nextTick 队列 :process.nextTick 函数关联的回调。
  • Promises 队列 :保存与 JavaScript 中原生 Promise 相关的回调

微任务队列

nextTick 队列的优先级高于 Promises 队列

Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
process.nextTick(() => console.log("this is process.nextTick 1"));

计时器队列

微任务的优先级高于计时器队列(宏任务) 定时器队列回调按先进先出 (FIFO) 顺序执行

I/O 队列

看资料文档 最佳 主要的意思是说,计时器队列的优先级虽然是高于 I/O 队列的,但是因为在底层的 C 代码上,又 1 毫秒的下限,就是说最低将函数扔进计时器队列的延迟不能小于 1ms,即使你是设置为 0 也会变成 1ms。然后就是 事件循环的开始时间,比如事件循环在 0.05ms 的时候开始循环,此时计时器队列还没有进入 callback,所以继续循环到 I/O 队列,1ms 了函数被扔进计时器队列,然后在下一次事件循环的时候开始执行。如果 cpu 繁忙,事件循环开始事件慢了,1ms 开始执行循环,此时计时器队列是又函数的,所以先执行计时器的 cb,然后继续循环。

<!-- 多次执行  发现输出结果并不一致 -->
const fs = require("fs");
setTimeout(() => console.log("this is setTimeout 1"), 0);
fs.readFile(\_\_filename, () => {
console.log("this is readFile 1");
});

事件循环中的I/O 轮询 (polling)

链接中的文章写的很好 重要的一点就是 仅当 I/O 完成后,才会轮询 I/O 事件并将回调函数添加到 I/O 队列中 也就是说 只有你的I/O任务完成了,回调函数才会被扔进I/O队列,等待事件循环迭代到他

// index.js
const fs = require("fs");

fs.readFile(__filename, () => {
  console.log("this is readFile 1");
});

process.nextTick(() => console.log("this is process.nextTick 1"));
Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
setTimeout(() => console.log("this is setTimeout 1"), 0);
--检查队列的输出发生在readFile I/O队列输出之前
setImmediate(() => console.log("this is setImmediate 1"));

for (let i = 0; i < 2000000000; i++) {}

检查队列

检查队列回调在微任务队列回调、定时器队列回调和I/O队列回调执行后执行。 检查队列中的回调是在微任务队列、定时器队列和I/O队列中的回调执行完之后执行的。在检查队列回调之间,执行微任务队列回调。setTimeout()当以0ms延迟运行setImmediate()方法时,执行顺序取决于CPU的工作负载

关闭队列

见名知意,,"关闭队列"(Close phase)是一个专门处理关闭事件的阶段。这个阶段主要涉及的是清理和关闭操作,如关闭 sockets、清理资源等 关闭队列回调在事件循环的给定迭代中的所有其他队列回调之后执行。