Node.js的事件循环详细分析

为了化简,我将官方的事件循环图进行了遮挡,只留下重点:timers, poll, check这三个对列。
Node.js Event Loop 的理解 Timers,process.nextTick()
上面这二篇文章对事件循环分析的很清楚了。我就只简要总结。
阶段总览 (Phases Overview)
- 计时器(timers):执行 setTimeout() 和 setInterval() 的回调,只要时间一到,就把回调放进去。
- I/O 回调: 执行几乎全部发生异常的 close 回调, 由定时器和setImmediate()计划的回调;
- 空闲,预备(idle,prepare):只内部使用;
- 轮询(poll): 获取新的 I/O 事件;nodejs这时会适当进行阻塞;
- 检查(check): 执行 setImmediate() 的回调;
- close callbacks: 例如 socket.on('close', ... );
var fs = require('fs');
function someAsyncOperation (callback) {
// 花费2毫秒
fs.readFile(__dirname + '/' + __filename, callback);
}
var timeoutScheduled = Date.now();
var fileReadTime = 0;
setTimeout(function () {
var delay = Date.now() - timeoutScheduled;
console.log('setTimeout: ' + (delay) + "ms have passed since I was scheduled");
console.log('fileReaderTime',fileReadtime - timeoutScheduled);
}, 10);
someAsyncOperation(function () {
fileReadtime = Date.now();
while(Date.now() - fileReadtime < 20) {
}
});
// 运行结果
setTimeout: 22ms have passed since I was scheduled
fileReaderTime 2
运行过程说明
- 首先将代码全部放到主线列队列中全部运行。下面代码运行完毕。
var fs = require('fs');
function someAsyncOperation (callback) {
// 花费2毫秒
fs.readFile(__dirname + '/' + __filename, callback);
}
var timeoutScheduled = Date.now();
var fileReadTime = 0;
- 当运行到setTimeout时,
setTimeout(function () {
var delay = Date.now() - timeoutScheduled;
console.log('setTimeout: ' + (delay) + "ms have passed since I was scheduled");
console.log('fileReaderTime',fileReadtime - timeoutScheduled);
}, 10);
node.js会启动一个定时线程定时,程序继续。
- 在运行到
someAsyncOperation(function () {
fileReadtime = Date.now();
while(Date.now() - fileReadtime < 20) {
}
});
- 发现有
fs.readFile(__dirname + '/' + __filename, callback);
这里又会启动一个文件读取线程去读文件。
- 这时程序发现poll里已经没有任务,就会阻塞在这里。
- 然后2ms以后,fs.read完成,就将回调打入poll里。
- 程序将一次性清空poll队列内容,放入主循环队列,一次执行。再次期间之前的setTimeout到时间,将回调打入到timers队列。
- 主循环队列运行完毕,检查check队列,就清空timers队列,再放入主循环队列,运行。
- 因为有了之前的运行时间消耗,所以等到运行setTimeout回调的时候,时间就到了22ms。
- 程序继续回到poll,监听其它动静。
process.nextTick()
process.nextTick()不在event loop的任何阶段执行,而是在各个阶段切换的中间执行,即从一个阶段切换到下个阶段前执行。
阶段切换就是指清空队列以后。
与浏览器事件的区别
最大区别就是,浏览器的事件循环队列是一个一个的弹出,而Node.js是一次性清空任务队列。