1. Node.js事件循环六个阶段
- timers:这个阶段执行
setTimeout
和setInterval
设定的回调函数。
- pending callbacks:执行系统底层的一些回调,比如TCP连接错误的回调。
- idle, prepare:仅系统内部使用。
- poll:这是事件循环最重要的阶段。在此阶段,事件循环会等待新的I/O事件,然后执行相关的回调。如果有已经到期的定时器,会立即执行并回到
timers
阶段。
- check:执行
setImmediate
设定的回调函数。
- close callbacks:执行一些关闭的回调函数,比如
socket.on('close', ...)
。
2. 优化策略
- 合理安排定时器:
- 避免过度使用
setTimeout
和setInterval
,因为大量定时器会增加事件循环timers
阶段的负担。
- 对于间隔执行的任务,可以考虑使用
setImmediate
结合process.nextTick
来模拟定时器,因为setImmediate
会在poll
阶段完成后执行,而process.nextTick
会在当前操作完成后立即执行,这样可以将任务分散到不同阶段,减少对timers
阶段的压力。
- I/O操作优化:
- 尽量使用异步I/O操作,Node.js的I/O操作大多是异步的,这能让事件循环在等待I/O完成时去处理其他任务。
- 对于多个I/O操作,可以使用
Promise.all
来并行执行,提高效率。但要注意,如果并行的I/O操作过多,可能会耗尽系统资源,所以要根据实际情况合理控制并行度。
- 可以将一些非紧急的I/O操作放在
setImmediate
中执行,让紧急的I/O操作先在poll
阶段处理,这样可以保证应用的响应性。
3. 代码示例
- 定时器优化示例:
// 使用setImmediate和process.nextTick模拟定时器
function asyncLoop(count, callback) {
let i = 0;
const loop = () => {
if (i < count) {
// 模拟一些工作
console.log(`Iteration ${i}`);
i++;
process.nextTick(loop);
} else {
setImmediate(callback);
}
};
process.nextTick(loop);
}
asyncLoop(10, () => {
console.log('All iterations completed');
});
- I/O操作优化示例:
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const readFileAsync = promisify(fs.readFile);
// 并行读取多个文件
const filePaths = ['file1.txt', 'file2.txt', 'file3.txt'].map(file => path.join(__dirname, file));
Promise.all(filePaths.map(filePath => readFileAsync(filePath, 'utf8')))
.then(data => {
console.log('All files read successfully:', data);
})
.catch(err => {
console.error('Error reading files:', err);
});
// 将非紧急I/O操作放在setImmediate中
setImmediate(() => {
fs.readFile('non - urgent - file.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading non - urgent file:', err);
} else {
console.log('Non - urgent file data:', data);
}
});
});