什么是事件循环
Event loop 允许node.js执行非阻塞I/O操作(尽管JS是单线程的),也就是在相应情况下,尽可能的将任务交给系统内核。
多数内核是多线程的,可以同时处理多个任务。当其中一个任务完成时,相应的callback被插入到轮询队列中,最终被执行。
事件循环
- 事件循环包含几个阶段,相应的阶段做相应的事
- 初始化: Node.js启动后,会进行一些初始化
- 初始化Event loop
- 处理目标脚本
- 然后进入事件循环
- 每个阶段,都有其FIFO队列,用来执行回调函数。
- 每个阶段都是特殊的。
- 当进行到该阶段时,会执行该阶段特有的操作,然后执行该阶段队列中的回调。
- 当队列空,或者达到执行次数限制,事件循环进行下阶段。
- 循环往复。
阶段总览
- timers:执行setTimeout()和setInterval安排的回调
- I/O callbacks: 执行几乎所有异常的close回调,由timer和setImmediate执行的回调。
- idle,prepare: 只用于内部
- poll : 获取新的I/O事件,node在该阶段会适当的阻塞
- check : setImmediate的回调被调用
- close callbacks: e.g socket.on(‘close’,…);
- 在每次运行事件循环之间,node.j检查是否有正在等待的异步i/o调用、timers等。如果没有,就清除并结束(退出程序),例如:执行一个程序,仅有一句话(var a= ‘hello’;),处理完目标代码后,不会进入evetloop,而是直接结束程序。
每个阶段
Timers:定时器可以设置回调函数在指定时间后运行。这个回调函数会在能够被调度后,尽可能快的运行。操作系统的调度,或其他回调数的运行,可能会延迟该回调函数的运行。**通常,poll阶段控制定时器的运行。
- 当事件循环进入poll阶段,根据该阶段队列内容执行:
- 执行队列中的回调函数
- 当队列中没有回调函数时,且定时器设置时间已过
- 回到timers 阶段,执行相应的回调函数
- **为了防止poll阶段过长,libuv根据为不同系统设置了poll阶段的最长事件。
I/O callbacks:该阶段执行一些如TCP错误之类的系统操作的会ID回调。
Poll(轮询):
两个主要功能
- 执行定时器设置并到期的回调函数,然后
- 处理poll队列中的事件
工作机制:当没有timers被调度,分两种情况
- 如果poll队列不为空,会挨个执行队列里的callback,直到队列为空,或达到系统限制
- 如果poll队列为空,分两种情况:
如果执行了setImmediate(),eventloop会结束poll阶段,进入到check阶段执行
如果没有执行setImmediate(),eventloop会等待callback进入队列
- 一旦poll队列为空,evetloop会检查timers,如果计时已到,event loop 会回到 timers 阶段,执行相应的回调函数.
check阶段
- poll阶段变为空闲、等待状态时,一旦调用setImmediate(),eventloop会进入check 阶段,而不是在poll阶段等待。
close callbacks阶段
- 例如:socket或句柄关闭,close事件会触发这个阶段。或者通过process.nextTick()触发
总结
- Node.js事件循环有实际上7、8个阶段,但我们关注-node实际使用的,就是前面说的几个。每个阶段都有自己的队列。本阶段执行完成后,执行下一个阶段。循环顺序不是完全固定的,很多阶段都是由外部事件触发的。
重要的三阶段:
timers,定时器阶段:
执行定时任务(setTimeOut(), setInterval())poll 轮询阶段:
- 处理到期的定时器任务,然后(因为最开始阶段队列为空,一旦队列为空,就会检查是否有到期的定时器任务)
- 处理队列任务,直到队列空,或达到上限
- 如果队列为空:如果setImmediate,终止轮询阶段,进入检查阶段执行。如果没setImmediate,查看有没有定时器任务到期,有的话就到timers阶段,执行回调函数.
check 检查阶段
- 轮询阶段空闲,且有setImmediate的时候,进入检查阶段
不重要二阶段
- I/O callbacks阶段:处理I/O异常错误
- close callbacks阶段: 处理各种close事件回调
代码
- 关于poll阶段和timers阶段调用先后
const fs = require('fs');
setTimeout(function(){
console.log('ff');
},0);
function func(cb) {
fs.readFile('xxx.js',cb);
}
func( () => {
console.log('a');
})