一般Web后端软件都没有很多的复杂计算,CPU很多时间都是在等待I/O操作完成或者等待网络应答消息,下图是CPU的一级缓存和二级缓存,硬盘,内存操作所花费的CPU cycles。
在本文中我们将说明如何理解Node.js事件轮询。
I/O即输入/输出,通常指数据在内部存储器和外部存储器或其他周边设备之间的输入和输出
不同硬件设备I/O操作所花费CPU cycles
Action Cost (CPU cycles)
L1 Cache* 3
L2 Cache* 14
RAM* 250
Disk 41,000,000
Network 240,000,000
Clock cycle 是计算机基本时间单位
举个网络延迟的示例
$ ping google.com
64 bytes from 172.217.16.174: icmp_seq=0 ttl=52 time=33.017 ms
.....
64 bytes from 172.217.16.174: icmp_seq=7 ttl=52 time=27.846 ms
假设在这个示例中网络大概延迟为44ms,CPU的速率为4GHz,如果CPU不等待网络消息的延迟的时间,176,000,000 CPU cycles大概等同可以完成多次CPU一,二级缓存以及内存的I/O操作。
Node.js怎么解决CPU等待I/O的问题
对与I/O的操作,都是使用异步的方式,不用等待I/O操作完成也可以继续执行,那么怎么做到异步执行,那就是事件轮询(Event Loop)
什么是Event Loop
Event Loop 是一个很重要的概念,指的是计算机系统的一种运行机制.下图Node.js事件轮询模型
taskQueue队列中蓝色的是MicrotaskQueue,taskQueue中包含MicrotaskQueue
那么它是怎么运作的,跟着下面的代码来理解Event Loop
console.log('script start')
const interval = setInterval(() => {
console.log('setInterval')
}, 0)
setTimeout(() => {
console.log('setTimeout 1')
Promise.resolve().then(() => {
console.log('promise 3')
}).then(() => {
console.log('promise 4')
}).then(() => {
setTimeout(() => {
console.log('setTimeout 2')
Promise.resolve().then(() => {
console.log('promise 5')
}).then(() => {
console.log('promise 6')
}).then(() => {
clearInterval(interval)
})
}, 0)
})
}, 0)
Promise.resolve().then(() => {
console.log('promise 1')
}).then(() => {
console.log('promise 2')
})
将会输出
script start
promise1
promise2
setInterval
setTimeout1
promise3
promise4
setInterval
setTimeout2
setInterval
promise5
promise6
第一个周期
setInterval,setTimeout 1任务被加入taskQueue,Promise.resolve 1和then被加入到microtaskQueue中,此时stack是空的,根据whatwg规范,在同一个周期内,应该先处理好microtaskQueue,Promise.resolve 1任务被执行
Task queue: setInterval, setTimeout 1
script start
promise1
promise2
第二个周期
microtask已经被处理完, setInteval
任务推入到stack中被执行,其它的setInterval
任务加入到TaskQueue队列中,排在setTimeout1任务后面
Task queue: setTimeout 1, setInterval
script start
promise1
promise2
setInterval
第三个周期
setTimeout1任务推入到stack中被执行,promise 3
和promise 4
加入 microtasksQueue,据whatwg规范promise 3
和promise 4
也会在同一周期被处理,setInterval任务和setTimeout2会被加入taskQueue队列
Task queue: setInterval, setTimeout 2
script start
promise1
promise2
setInterval
setTimeout1
promise3
promise4
第四个周期
setInterval任务推入到stack中被执行,其它的setInterval任务加入taskQueue队列,在setTimeout 2的后面
Task queue: setTimeout 2, setInteval
script start
promise1
promise2
setInterval
setTimeout1
promise3
promise4
setInterval
第五个周期
setTimeout 2
任务推入到stack中被执行, promise 5
and promise 6
加入到microtask队列并被执行
script start
promise1
promise2
setInterval
setTimeout1
promise3
promise4
setInterval
setTimeout2
setInterval
promise5
promise6
参考链接risingstack,注意各种浏览器之间会有所差异jakearchibald
Node.js内部实现
- V8
Google V8 javascript引擎,执行javascript代码
- Libeio
Libeio是一个包含全部特性(read, write, open, close, stat, unlink, fdatasync, mknod, readdir)负责I/O异步操作库
也就是事件模型图中的后台线程部分
- Libev
高性能的事件轮询模型,负责Microtasks和Macrotasks队列的轮询
事件模型中除开后台线程部分,其余都由Libev负责