Node的事件循环机制
一、什么是事件循环
在node应用程序启动后,并不会立即进入事件循环,而是先执行输入代码,从上到下开始执行,同步API立即执行,异步API交给C++维护libuv的线程执行,异步API的回调函数被注册到对应的poll事件队列中。当所有输入代码执行完成后,开始进入事件循环
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
//注意:途中每个方框被称为事件循环的一个阶段,这六个阶段为一轮事件循环 Event loop
- timer阶段(定时器):执行setTimeout(callback)和setInterval(callback)
- I/O callbacks阶段(I/O回调):执行某些系统操作的回调(例如TCP错误类型)
- idle、prepare阶段(空转):仅node内部使用
- poll阶段(轮询):获取新的I/O事件,例如操作读取文件
- check阶段(检查):执行
setImmediate()
设定的callbacks; - close callbacks阶段(关闭回调):比如
socket.on(‘close’, callback)
的callback会在这个阶段执行;
如果event loop进入了poll阶段,且代码未设定timer,将会发生下面情况:
-
如果poll queue不为空,event loop将同步的执行queue里面的callback,直至queue为空,或者执行的callback到达系统上限制
-
如果poll queue为空:
- 如果代码已经被setImmediate()设定callback,event loop将结束poll阶段进入 check阶段并执行check阶段的queue
- 如果代码没有设定setImmediate(callback),event loop将阻塞在该阶段等待callback加入poll queue,一旦达到就立即执行
如果event loop 进入poll阶段,且代码设定了timer:
-
如果poll queue进入空状态时(即poll阶段为空闲状态),event loop将检查timers,如果1个或多个timers时间已达到,event loop将按循环顺序进入timers阶段,并执行timer queue
二.nextTick
与setImmediate
process.nextTick
不属于时间循环的任何一个阶段,它属于该阶段与下阶段之间的过渡,即本阶段执行结束,进入下一个阶段前,所以要执行回调(插队)setImmediate
的回调处于check阶段,当poll阶段的队列为空,且check阶段的时间队列存在的时候,要切换到check阶段执行- setImmediate()被设计在 poll 阶段结束后立即执行回调;
- setTimeout()被设计在指定下限时间到达后执行回调;
setTimeout(() => {
setTimeout(() => {
console.log(1);
}, 0);
setImmediate(() => {
console.log(2);
});
}, 0);
// 2 1
setTimeout(() => {
console.log(1);
}, 0);
setImmediate(() => {
console.log(2);
});
// 1 2或 2 1
三.nextTick和Promise
理解成一个微任务,不属于事件循环的一部分,会在其所处的时间循环最后,时间循环进入下一个阶段前执行
console.log("start");
setTimeout(() => {
console.log("timer1");
Promise.resolve().then(function () {
console.log("promise1");
});
}, 0);
setTimeout(() => {
console.log("timer2");
Promise.resolve().then(function () {
console.log("promise2");
});
}, 0);
Promise.resolve().then(function () {
console.log("promise3");
});
console.log("end");
process.nextTick(() => {
console.log(1);
});
//start->end->1->promise3->timer1->promise1->timer2->promise2
Node是单线程的,主线程将所有任务都放在循环队列中,然后由底层的libuv库从循环事件队列中取出任务分配给不同的线程去处理,主线程同时也会进行回调处理,整个过程形成事件循环