Node.js中event-loop机制是怎样,如何掌握
Admin 2022-05-19 群英技术资讯 759 次浏览
这篇文章主要介绍“Node.js中event-loop机制是怎样,如何掌握”,有一些人在Node.js中event-loop机制是怎样,如何掌握的问题上存在疑惑,接下来小编就给大家来介绍一下相关的内容,希望对大家解答有帮助,有这个方面学习需要的朋友就继续往下看吧。
今天我们来学习下nodeJs中的event-loop。event-loop的理解对我来讲一直都是一个比较大的难点,希望通过这次的学习把这个难点突破,也希望能通过这篇博客加深自己对event-loop的理解和印象。
在学习event-loop之前,先了解下node的libuv。libuv负责不同操作系统上的不同I/O模型的实现,并且把不同的实现抽象为能应用与第三方应用程序的API。

在正式学习event-loop前,先思考一个问题
setTimeout(() => {
console.log("timer1");
Promise.resolve().then(() => {
console.log("promise1");
});
}, 0);
setTimeout(() => {
console.log("timer2");
Promise.resolve().then(() => {
console.log("promise2");
});
}, 0);这段代码在浏览器中运行的结果是怎样的?

在node中运行的结果又是怎样的呢?
在node8.6之前:
node8.6之后:

为什么会有这样的结果,我们稍后分析!
首先,来看一张图:

在图中可以看到6个阶段,分别是:timers,pending callbacks,idle/prepare,poll,check,close callbacks。
timers阶段:主要执行setTimeOut,setInterval的回调
pending callbacks阶段:执行一些系统调用的错误,比如说网络通信的错误回调
idle/prepare阶段:只在系统内部使用(这个阶段我们控制干涉不了)
poll阶段:获取新的I/O事件,比如获取一个读取文件的I/O回调。在适合的情况下,nodejs将阻塞在这个阶段
check阶段:执行 setImmediate的回调
比如执行sokect的destory,close事件回调
每一个阶段都遵循一个FIFO(先入先出)的规则来执行任务队列里面的任务。 在这六个阶段中,我们着重需要关注的是timers,poll,check阶段。我们日常开发中的绝大部分异步任务都是在这三个阶段处理的。
我们先来说一说timers阶段。
timers是事件循环的第一个阶段,nodejs会去检查有没有已经过期了的timer,如果有,就将它的回调放入队列中。但是nodejs并不能保证timer在预设事件到了就会立即执行回调,这是因为nodejs对timer的过期检查不一定靠谱,它会受机器上其他运行程序的影响,或者是会遇到当前主线程不空闲的情况。
对于这里的不确定性,官网上举了一个例子:
先声明一个setTimeOut,然后外部读取一个文件,当读取文件操作超过定时器的时间,这样一来读文件操作就会把定时器的回调延后,这就是前面说的主线程不空闲的情况。
poll阶段主要是执行两件事情:
1、处理poll阶段的任务队列
2、当有了已经超时的timer执行它的回调函数

在上图中,我们还可以看到:在poll阶段执行完poll任务队列的任务之后,会去检查有无预设的setImmediate,如果有,则进入check阶段,如果没有,则nodejs将会阻塞在这里。
这里我们就会有一个疑问了,如果阻塞在poll阶段,那我们设置的timer岂不是执行不了了吗?
其实当event-loop阻塞在poll阶段时,nodejs会有一个检查机制,它会去检查timers队列是否为空,如果不为空,则重新进入timers阶段。
check阶段主要时执行setImmediate的回调函数。
event-loop的每个阶段都有一个队列,当event-loop达到某个阶段之后,将执行这个阶段的任务队列,直到队列清空或者达到系统规定的最大回调限制之后,才会进入下一个阶段。当所有阶段都执行完成一次之后,称event-loop完成一个tick。
上面我们说完了event-loop的理论部分,但是光有理论我们也还是不能很清晰的理解event-loop。下面我们就根据几个demo来更加深入的理解下event-loop!
demo1
const fs=require('fs')
fs.readFile('test.txt',()=>{
console.log('readFile')
setTimeout(()=>{
console.log('settimeout');
},0)
setImmediate(()=>{
console.log('setImmediate')
})
})执行结果:

可见执行结果跟我们前面的分析时一致的!
demo2
const fs = require("fs");
const EventEmitter = require("events").EventEmitter;
let pos = 0;
const messenger = new EventEmitter();
messenger.on("message", function (msg) {
console.log(++pos + " message:" + msg); //
});
console.log(++pos + " first"); //
process.nextTick(function () {
console.log(++pos + " nextTick"); //
});
messenger.emit("message", "hello!");
fs.stat(__filename, function () {
console.log(++pos + " stat"); //
});
setTimeout(function () {
console.log(++pos + " quick timer"); //
}, 0);
setTimeout(function () {
console.log(++pos + " long timer"); //
}, 30);
setImmediate(function () {
console.log(++pos + " immediate"); //
});
console.log(++pos + " last"); //结果:

在node 8.6 之前:
浏览器中的微任务队列会在每个宏任务执行完成之后执行,而node中的微任务会在事件循环的各个阶段之间执行,即每个阶段执行完成之后会去执行微任务队列。
在8.6之后:
浏览器和node中微任务的执行是一致的!
所以,在文章开头,我们提出的思考的问题就有了结果。
语法:process.nextTick(callback,agrs)
执行时机:
这个函数其实是独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行。递归的调用process.nextTick()会导致I/O starving,官方推荐使用setImmediate()
关于starving现象的说明:
const fs = require("fs");
fs.readFile("test.txt", (err, msg) => {
console.log("readFile");
});
let index = 0;
function handler() {
if (index >= 30) return;
index++;
console.log("nextTick" + index);
process.nextTick(handler);
}
handler();运行结果:

可以看到,等到nextTick函数呗执行30次之后,读取文件的回调才被执行!这样的现象被称为 I/O 饥饿。
当我们把 process.nextTick 换为 setImmediate
const fs = require("fs");
fs.readFile("test.txt", (err, msg) => {
console.log("readFile");
});
let index = 0;
function handler() {
if (index >= 30) return;
index++;
console.log("nextTick" + index);
setImmediate(handler);
}
handler();结果:

造成这两种差异的原因是,嵌套调用的setImmediate的回调被排到了下一次event-loop中去!

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这篇文章主要给大家介绍了关于JS如何实现页面截屏功能的相关资料,文中主要利用了html2canvas和canvas绘制两个方法来实现,需要的朋友可以参考下
本篇文章带大家了解一下Node中的文件模块和核心模块,聊聊文件模块的查找和文件模块的编译执行、JavaScript与C/C++ 核心模块的编译执行,希望对大家有所帮助!
字符串可以包含在单引号、双引号或反引号中。单引号和双引号基本相同。反引号允许我们通过${…}在字符串中嵌入任何表达式。允许字符串换行。
前些日子了解到mqtt这样一个协议,可以在web上达到即时通讯的效果,所以下面这篇文章主要给大家介绍了关于vue中如何利用mqtt服务端实现即时通讯的相关资料,需要的朋友可以参考下
本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于弱映射与弱集合的相关问题,下面一起来看一下,希望对大家有帮助。
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008