Node 事件循环

我们知道,Node是基于Chrome V8 引擎的,也就是说它也有js引擎的事件循环也就是 Event Loop 机制。但是Node是运行在服务端的,是有区别于浏览器端的。比如比浏览器的异步API多了 setImmediate, process.nextTick 等。那么事件循环的机制也肯定是不一样的。下面我们就来看下 Node 的事件循环是怎么样的一个运作。

Node 事件循环

1. Node 的事件循环可以简单地看成是由一个 线程(即js主线程) 跟 好几个队列(比如nextTickQueue, microTaskQueue 等)组成的。主线程在不停地从队列中获取任务并执行,类似于在 while(true) {} 里面一直不断地循环

2. 事件循环是在主线程上面完成的

3. 事件循环包括同步任务跟异步任务,同步任务的执行优先级大于异步任务,也就是同步任务永远是在异步任务之前执行的:

 1 console.log('我是同步任务,第一个输出')
 2 process.nextTick(() => {
 3    console.log('我是异步任务,第三个输出') 
 4 })
 5 setTimeout(() => {
 6     console.log('我是异步任务,第四个输出')
 7 })
 8 setImmediate(() => {
 9     console.log('我是异步任务,第五个输出')
10 })
11 console.log('我是同步任务,第二个输出')
12 // 输出结果
13 // 我是同步任务,第一个输出
14 // 我是同步任务,第二个输出
15 // 我是异步任务,第三个输出
16 // 我是异步任务,第四个输出
17 // 我是异步任务,第五个输出
View Code

4. 为了方便理解,这里暂且把异步任务分为我们常说的微任务(microTask)宏任务(macroTask)microTask包含nextTickQueuemicroTaskQueue(为了方便理解,后面的microTask都是指nextTickQueuemicroTaskQueue)macroTask包含 setTimeout/setInterval/setImmediate/IO callback/http callback 等。其中 microTask 会在当前循环中执行,而 macroTask则是当当前循环里面的同步任务跟microTask里面的任务执行完之后才会执行。而在执行macroTask的时候,又会优先执行里面的同步任务,然后再检查 microTask 里面有没有任务,有的话执行完再继续下一个循环,没有的话则直接执行下一个宏任务。这样一轮一轮无限循环知道服务退出。这就是事件循环。

5. 在当前循环中,js线程的执行顺序是这样的:同步任务 > nextTickQueue > microTaskQueue ,也就是先执行同步任务,同步任务执行完之后检查 nextTickQueue 队列里面有没有任务,有的话执行 nextTickQueue,直到 nextTickQueue 里面的任务执行完之后,再判断 microTaskQueue 队列有没有任务,有的话执行,没有的话继续下一循环。需要注意的是当执行 microTaskQueue 任务时,如果又出现 process.next() 跟 Promise.then(), 也会先执行完 microTaskQueue,直到 microTaskQueue 为空,再判断 nextTickQueue :

 1 console.log('1')
 2 process.nextTick(() => {
 3     console.log('tick 1')
 4     process.nextTick(() => {
 5         console.log('tick 2')
 6     })
 7 })
 8 
 9 Promise.resolve().then(() => {
10     console.log('Promise.then 1')
11     process.nextTick(() => {
12         console.log('tick 3')
13     })
14     Promise.resolve().then(() => {
15         console.log('Promise.then 2')
16     })
17 })
18 // 输出结果:
19 // 1
20 // tick 1
21 // tick 2
22 // Promise.then 1
23 // Promise.then 2
24 // tick 3
View Code

6. 当执行完当前宏任务之后,又会去队列里面判断是否有宏任务,有的话拿出来继续执行。

7. 事件循环的架构

 8. 由上图我们可以看到,事件循环分为6个阶段,这些阶段是按照图中顺序依次执行的。每个阶段都有自己的一个队列,存放着相应的任务,每执行一个阶段都会遍历这个阶段里面的任务,依照先进先出原则执行,只有当一个阶段的任务都执行完之后才会执行下一个阶段。这里需要注意的是 nextTickQueue 跟 microTaskQueue 是在每个阶段的每个任务执行的时候都会区执行的。也就是说如果 Timer 队列里面有三个任务, 则每执行一个任务都会去判断 nextTickQueue 跟 microTaskQueue 是否有任务,有的话先执行,执行完之后再执行下一个任务。如果这两个队列一直执行不完,就会导致后续的任务阻塞,最后系统崩溃。所以我个人理解为 microTask 是依附在 macroTask 的,每个 macroTask 都可能包含自己的 microTask,只有当当前 macroTask 附属的microTask 执行完之后才会执行下一个 macroTask。

9. 下面简单说下每个阶段分别是什么:

  1). Timer 阶段:定时器阶段,主要处理setTimeout/setInterval 回调函数。当进入这个阶段的时候,主线程会判断当前时间是否满足定时器要求,即定时器设置的延迟时间是否到了,是的话就执行,否则进入下一个阶段。

  2). Pending I/O callbacks 阶段:IO回调阶段,处理IO回调函数。

  3). Idle, prepare 阶段:node 内部操作,具体也不清楚

  4). Poll 阶段:轮询,等待还未返回的IO事件

  5). Check 阶段:执行 setImmediate 回调。

  6). Close callbacks:执行关闭请求的回调,比如 socket.on('close', () => {})

通过这6个阶段的大概说明,我们可以知道,其实当 setTimeout 延迟时间为0(理想状态下) 的时候,其实是优先执行于 setImmediate 的,但是在实际操作中,这点是不好保证的,因为setTimeout 可能会有一点延迟,导致在 Timer 阶段这个回调函数还未进去队列,所以 setImmediate 会优先执行于 setTimeout。这取决于机器性能跟其他一些不可控因素。但是以下代码却能确保 setImmediate 是优先于 setTimeout 的,为啥?因为 setImmediate 是处于第五阶段,而 IO 回调是第二阶段,setTimeout 是第一阶段。当执行IO 回调的时候,第一阶段(setTimeout)已经跑完了,按顺序第五阶段(setImmediate)会先跑。

 1 fs.readFile('./test1.js', 'utf-8', () => {
 2     setTimeout(() => {
 3         console.log('setTimeout in IO callback')
 4     })
 5     setImmediate(() => {
 6         console.log('setImmediate in IO callback')
 7         process.nextTick(() => {
 8             console.log('process in IO-setImmediate callback')
 9         })
10     })
11     process.nextTick(() => {
12         console.log('process in IO callback')
13     })
14     console.log('sync in IO callback')
15 })
16 // 输出结果:
17 // sync in IO callback
18 // process in IO callback
19 // setImmediate in IO callback
20 // process in IO-setImmediate callback
21 // setTimeout in IO callback
View Code

 setImmediate 跟 setTimeout 比较:setTimeout 会计算当前任务是否可执行,可以才执行。而 setImmediate 不需要计算,而是直接放入队列中,所以 setImmediate 性能相对于 setTimeout 会好一些

猜你喜欢

转载自www.cnblogs.com/l-c-blog/p/11746410.html