Node.js中event-loop机制是怎样,如何掌握
Admin 2022-05-19 群英技术资讯 979 次浏览
这篇文章主要介绍“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进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
本篇文章带大家了解一下ECMAScript 新提案:JSON模块,来看看JSON模块是如何工作的,希望对大家有所帮助!
这篇文章主要介绍了javascript的一些基础知识,内容很全面,特别适合刚刚学习javascript的朋友学习,希望能帮助到大家
这篇文章主要介绍了JS实现页面侧边栏效果探究,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
这篇文章给大家分享的是有关vue框架中封装方式的内容。小编觉得挺实用的,因此分享给大家做个参考,相信会对新手理解封装有一定的帮助,接下来一起跟随小编看看吧。
这篇文章主要为大家详细介绍了JavaScript实现简单的拖拽效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
成为群英会员,开启智能安全云计算之旅
立即注册关注或联系群英网络
7x24小时售前:400-678-4567
7x24小时售后:0668-2555666
24小时QQ客服
群英微信公众号
CNNIC域名投诉举报处理平台
服务电话:010-58813000
服务邮箱:service@cnnic.cn
投诉与建议:0668-2555555
Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 ICP核准(ICP备案)粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008