node中的事件循环

node 异步I/O的4要素:请求对象,线程池,观察者,事件循环

event-loop 查看官网介绍 https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/


   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

Note:

poll

在node.js里,任何异步方法(除timer,close,setImmediate之外)完成时,都会将其callback加到轮询队列poll queue里,并立即执行。

1.计算它应该block和轮询I/O的时间
2.处理轮询队列中的事件,执行定时器的回调。

当event loop进入poll阶段并且此前代码中没有设置定时器时,有以下两种情况:

1.如果轮询队列不为空,则事件循环将遍历并同步执行它们的回调,直到队列为空或者达到系统上限。
2.如果轮询队列为空,则:
如果已经调用过setImmediate,则事件循环将结束poll阶段并继续执行check阶段,来执行这些定时器。
如果没有调用过setImmediate,则事件循环将阻塞在该阶段,等待有定时器回调加入到poll queue后立即执行。

当event loop进入poll阶段,并且代码设置了定时器:

如果poll是空闲状态,事件循环将检查已达到时间阈值的定时器。如果一个或多个定时器准备就绪,事件循环将回到 timers 阶段来执行那些定时器回调。

check

setTimeout/setInterval回调在timers阶段执行;而setImmediate在check阶段执行,总是在poll阶段完成后执行。

执行定时器的顺序将根据调用它们的上下文而有所不同, 原因详见(cnode讨论帖)[https://cnodejs.org/topic/57d68794cb6f605d360105bf]。

在非I/O周期内调用时,受进程性能限制,执行顺序不确定。(毕竟setTimeout的创建要涉及到红黑树等复杂操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// demo1: 打印顺序不定
setTimeout(() => {
console.log('timeout');
}, 0);

setImmediate(() => {
console.log('immediate');
});

// demo2: 打印顺序:immediate, timeout
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
});

使用setImmediate优于setTimeout的主要优点是setImmediate将始终在任何定时器之前执行(如果在I/O周期内调度),与存在多少定时器无关。

process.nextTick vs setImmediate

process.nextTick 在各个阶段的切换中间执行,可以理解为总是在当前阶段的末尾触发执行
setImmediate 只在check阶段执行,可以理解为在事件循环的后续迭代或’tick’中触发

其实这两个名字应该换一下,一般来讲process.nextTick比setImmediate更快地触发。

process.nextTick中继续调用nextTick很容易导致饿死现象,官方推荐使用setImmediate来实现异步。

-------------本文结束 感谢您的阅读-------------