node学习—深入剖析node生命周期(事件循环)
深入剖析node生命周期(事件循环)
要想学好本篇博文,需要先了解js的事件循环: js事件循环原理.
一、node生命周期
提一嘴:node底层使用C语言libuv库操控操作系统
由图,从运行模块入口main.js后,event loop会检测整个环境是否还有事件未结束,如文件读写等,定时器等。当所有事件全部执行完毕时,才会结束运行。
其中每一个阶段都会维护一个事件队列,这里主讲其中三个。
1.timers
timers:存放计时器的回调函数。
timers内部相当于一个循环在不停侦听定时器是否到达指定执行选时间。
setTimeout(()=>{
console.log("1s后输出");
},1000);
console.log('abcd');
此时timers将()=>{console.log("1s后输出")}
回调函数执行后即清空了它的队列。
2.poll
poll:轮询队列,绝大部分回调都会放入该队列,比如:文件的读取、监听用户请求。
setTimeout(function f1(){
console.log("1s后输出");
},1000);
console.log('abcd');
还是这段代码,f1要等1s的事件,所以timers里没有回调函数,然后到poll轮训队列,不停地查看是否有回调,1s后f1出现了,将f1放入timers队列,下一步check也没有。再来到timers,执行f1,清空了timers队列。然后又poll轮询,最终没有任何回调了,over。
setTimeout(function f1(){
console.log("1s后输出");
},1000);
const http = require("http");
const server = http.creareServer((req,res)=>{
console.log("request in");
});
server.listen(9527);
到达event loop事件循环,查看是否有值得等待的事件。此时timers还没有回调函数,然后到了poll轮询队列,不停地查看是否有回调,此时f1出现了,将f1放入timers队列,下一步check也没有。再来到timers,执行f1,清空了timers队列。又到了poll轮询,又一直等待用户请求触发createServer回调,等待很长时间仍未触发,则进入下一步check,然后又timers,poll…知道用户出发该回调函数后才能停止循环over。
接下来看这一段代码
const start = Date.now();
setTimeout(function f1(){
console.log("setTimeout: ",Date.now() - start);
},200);
const fs = require("fs");
fs.readFile("./index.js","utf-8",(err,data)=>{
console.log("readFile");
const start = Date.now();
while (Date.now() - start < 300){}
});
此时问题来了,怎么setTimeout等待了509ms呢
再看看这张图:
到达event loop事件循环,此时timers中没有回调。到poll轮询,此时发现需要读取文件,等待文件读取,并继续轮询,(如果文件较小)此时文件读取完成,触发了回调f2,将f2放入poll队列。执行f2,输出readFile,准备等待300ms后结束f2。等待期间此时f1好了,将f1放入timers队列,但是f2在执行中,所以继续执行f2,300ms后f2结束。发现此时没有任何需要等待的事。下一步check,没有事。又循环到timers,执行f1清空timers队列,接着poll发现没有任何事了,下一步,check也没有,最后over。所以这里显示setTimeout最终等待了509ms。
3.check
check:检查阶段,使用setImmediate的回调会直接进入这个队列
setImmediate相当于setTimeout(()=>{},0)
,一旦发现有setImmediate就会直接放入check队列。效率比timers高很多。
let i = 0;
console.time();
function test(){
i++;
if(i < 100){
setTimeout(test,0);
}else{
console.timeEnd();
}
}
test();
let i = 0;
console.time();
function test(){
i++;
if(i < 100){
setImmediate(test);
}else{
console.timeEnd();
}
}
test();
因为setTimeout每次需要判断时间放入timers队列,setImmediate没有计时直接放入check队列,所以setTimeout效率远远低于setImmediate。
但是setImmediate(()=>{})
和setTimeout(()=>{},0)
的执行顺序就不一定了,得看计算机当时的执行环境和速度,且setTimeout一般取不到0,至少都是1
const fs = require("fs");
fs.readFile("./index.js",()=>{
setTimeout(()=>console.log(1));
setImmediate(()=>console.log(2));
});
从主线程到event loop事件循环,此时timers没有回调事件,接着到poll轮询,等待文件读取完成,执行回调函数,将setTimeout放入timers,setImmediate放入check。下一步check执行setImmediate。接着循环,timers执行setTimeout,poll轮询不到任何回调事件,over。所以这种情况下,setImmediate必先执行与setTimeout之前。
此时回想js的事件循环,讲到了宏任务和微任务,那么在node中是怎么回事呢?
所以我们前面讲到的事件循环相当于宏任务,而nextTick和Promise相当于微任务。其实它们不是事件循环的一部分,node希望优先用最快的速度去执行它们,其中nextTick优先度最高。
看下面这一面试题,答案就显而易见了
setTimeout(() => {
console.log(1);
});
process.nextTick(()=>{
console.log(2);
process.nextTick(()=>{
console.log(6);
});
});
console.log(3);
Promise.resolve().then(()=>{
console.log(4);
process.nextTick(()=>{
console.log(5)
});
});
看下面这一道面试题
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function () {
console.log("setTimeout0");
}, 0);
setTimeout(function () {
console.log("setTimeout3");
}, 3);
setImmediate(() => console.log("setImmediate"));
process.nextTick(() => console.log("nextTick"));
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
console.log("promise2");
}).then(function () {
console.log("promise3");
});
console.log("script end");
我们根据代码顺序一步一步来:
console.log("script start");
timers:0ms_setTimeout0;3ms_setTimeout3
check: setImmediate
nextTick: console.log("nextTick"));
console.log("async1 start");
console.log("async2");
Promise:console.log("async1 end");
console.log("promise1");
console.log("promise2");
Promise:console.log("async1 end"); console.log("promise3");
console.log("script end");
所以我们得出同步的结果:
console.log("script start");
-
console.log("async1 start");
此时await async2();
后为等待,即为Promise队列,所以console.log("async1 end");
放入Promise队列 console.log("async2");
console.log("promise1");
console.log("promise2");
console.log("script end");
接下来我们查看队列:
console.log("nextTick");
console.log("async1 end")
但是后面就有问题了
setTimeout(function () {
console.log("setTimeout0");
}, 0);
setTimeout(function () {
console.log("setTimeout3");
}, 3);
setImmediate(() => console.log("setImmediate"));
我们不能确定得到同步的结果时是否消耗了3ms,所以不能timers队列里是否有setTimeout0或setTimeout3,最终不能确定setImmediate的实行顺序。
在作者电脑里运行时,得到同步结果时显然已经超过了3ms,所以第一次事件循环时timers队列里已经有setTimeout0和setTimeout3了。
上一篇: 112 路径总和(递归)
下一篇: 94.二叉树中序遍历
推荐阅读
-
深入浅析Node.js 事件循环、定时器和process.nextTick()
-
深入浅析Node.js 事件循环、定时器和process.nextTick()
-
node学习—深入剖析node生命周期(事件循环)
-
我的Node.js学习之路(三)--node.js作用、回调、同步和异步代码 以及事件循环_node.js
-
Node.js非阻塞IO和事件循环学习总结
-
【Node.js】学习记录6-node里的异步编程和事件循环
-
Node.js的事件循环工作流程以及生命周期的详细讲解
-
深入理解Node.js 事件循环和回调函数
-
深入浅析Node.js 事件循环_node.js
-
深入理解Node.js 事件循环和回调函数