简介
单线程
提到node,我们就可以立刻想到单线程、异步IO、事件驱动等字眼。首先要明确的是node真的是单线程的吗,如果是单线程的,那么异步IO,以及定时事件(setTimeout、setInterval等)又是在哪里被执行的。
其实,按照严格来说,node并不是单线程的。node中存在着多种线程,包括:
js引擎执行的线程
定时器线程(setTimeout, setInterval) : 倒计时结束后(异步)会把回调 放在事件队列里面
异步http线程(ajax)
node中的单线程是指js引擎只在唯一的主线程上运行
事件循环概述
主线程将所有任务都放在循环队列中,
然后由底层的libuv库从循环事件队列中取出任务分配给不同的线程去处理,
主线程同时也会进行回调处理,整个过程形成事件循环
nodejs实现异步机制的核心便是libuv,libuv承担着nodejs与文件、网络等异步任务的沟通桥梁
通过事件驱动模型实现了高并发和异步 I/O ,适合处理I/O密集型任务
六个阶段
node的事件循环共有六个阶段,在一次事件循环中这六个阶段按顺序会一直循环执行,直至事件处理完成。
每一个阶段有一个队列,event loop执行到该阶段时,会该阶段的队列里的所有callback,当队列callback为空时,或callback执行到上限的时,就跳至下一阶段进行执行。
各阶段描述
timers 阶段: 这个阶段执行timer(setTimeout、setInterval)的回调,该阶段新创建的setTimeout、setInterval 会继续放在下一个轮回循环阶段执行
pendding callbacks 阶段: 执行一些系统操作的回调(比如网络通信的错误回调);
idle, prepare 阶段: 仅node内部使用;
poll 阶段: 获取新的I/O事件, 适当的条件下node将阻塞在这里(比如:文件读取操作,http请求 回调 )
底层代码在poll阶段执行的时候,会传入一个timeout超时时间,timeout超时时间到了,则退出poll阶段,执行下一个阶段。
check 阶段: 执行setImmediate() 设定的callbacks;
close callbacks 阶段: 比如socket.on(‘close’, callback)的callback会在这个阶段执行。
事件循环执行过程
-
执行前,会先执行同步任务、在执行process.nextTick 、微任务 ,在进入timers阶段
-
在事件循环的每一个子阶段退出之前都会执行:process.nextTick(先执行于microtaks) microtaks.如果有的话
-
node 的初始化
初始化 node 环境。
执行输入代码
执行 process.nextTick 回调。
执行 microtasks。 -
进入 event-loop
-
进入 timers 阶段
-
进入IO callbacks阶段。
-
进入 idle,prepare 阶段:
-
进入 poll 阶段
-
进入 check 阶段。
-
进入 closing 阶段。
-
检查是否有活跃的 handles(定时器、IO等事件句柄)。
如果有,继续下一轮循环。
如果没有,结束事件循环,退出程序。
例子
const fs = require('fs');
setTimeout(() => {
// 新的事件循环的起点
console.log('1');
});//没填默认为1,主线程执行时间大于1ms,所以在timer阶段先执行。如果设置为2ms的话,则会先执行setImmediate
// 异步io,读取需要时间,当setTimeout时间较短的时候,setTimeout会比它先
fs.readFile('./text.txt', { encoding: 'utf-8' }, (err, data) => {
if (err) throw err;
console.log('3');
});
setImmediate(() => {
console.log('2');
});
// 1 2 3