您现在的位置是:网站首页 > 事件循环的阶段划分文章详情
事件循环的阶段划分
陈川
【
Node.js
】
14672人已围观
7138字
事件循环的阶段划分
Node.js 的事件循环是其异步 I/O 模型的核心,负责处理非阻塞操作。事件循环分为多个阶段,每个阶段执行特定类型的任务。理解这些阶段对于编写高效、可预测的 Node.js 应用至关重要。
定时器阶段(Timers)
定时器阶段处理 setTimeout
和 setInterval
注册的回调函数。当定时器到期时,对应的回调会被放入事件队列,等待执行。
setTimeout(() => {
console.log('Timeout 1');
}, 1000);
setInterval(() => {
console.log('Interval 1');
}, 2000);
需要注意的是,定时器的执行时间并不精确。如果其他操作阻塞了事件循环,定时器可能会延迟执行。
待定回调阶段(Pending Callbacks)
这个阶段执行一些系统操作的回调,比如 TCP 错误。例如,当 TCP 套接字收到 ECONNREFUSED 时,相关的错误回调会在这个阶段执行。
const net = require('net');
const client = net.createConnection({ port: 8080 }, () => {
console.log('Connected to server!');
});
client.on('error', (err) => {
// 这个错误回调会在待定回调阶段执行
console.error('Connection error:', err);
});
闲置、准备阶段(Idle, Prepare)
这是事件循环的内部阶段,通常不需要开发者直接干预。Node.js 在这个阶段进行一些内部准备工作。
轮询阶段(Poll)
轮询阶段是事件循环的核心阶段,它有两个主要功能:
- 计算应该阻塞和轮询 I/O 的时间
- 处理轮询队列中的事件
const fs = require('fs');
fs.readFile('/path/to/file', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
// 其他同步代码
console.log('Reading file...');
在这个例子中,文件读取操作的回调会在轮询阶段执行,而同步的 console.log
会立即执行。
检查阶段(Check)
setImmediate
注册的回调在这个阶段执行。它总是在轮询阶段之后立即执行。
setImmediate(() => {
console.log('Immediate 1');
});
setTimeout(() => {
console.log('Timeout 2');
}, 0);
有趣的是,虽然 setTimeout
的延迟为 0,但 setImmediate
的回调通常会先执行,因为它在检查阶段,而 setTimeout
在定时器阶段。
关闭回调阶段(Close Callbacks)
这个阶段处理一些关闭事件的回调,比如 socket.on('close', ...)
。
const server = require('net').createServer();
server.on('connection', (socket) => {
socket.on('close', () => {
console.log('Socket closed');
});
});
server.listen(8080);
当套接字关闭时,相关的回调会在这个阶段执行。
阶段执行顺序
事件循环的阶段按照以下顺序执行:
- 定时器
- 待定回调
- 闲置、准备
- 轮询
- 检查
- 关闭回调
这个过程会不断重复,直到没有更多的工作需要处理。
微任务队列
除了这些主要阶段,Node.js 还有微任务队列(Microtask Queue),包括:
process.nextTick
- Promise 回调
这些微任务会在当前阶段结束后立即执行,优先于下一个阶段。
Promise.resolve().then(() => {
console.log('Promise resolved');
});
process.nextTick(() => {
console.log('Next tick');
});
setImmediate(() => {
console.log('Immediate');
});
执行顺序会是:nextTick → Promise → Immediate。
阶段转换示例
const fs = require('fs');
// 阶段1:定时器
setTimeout(() => {
console.log('Timeout');
}, 0);
// 阶段4:轮询
fs.readFile('/path/to/file', () => {
// 阶段5:检查
setImmediate(() => {
console.log('Immediate inside readFile');
// 微任务
process.nextTick(() => {
console.log('NextTick inside Immediate');
});
});
// 微任务
Promise.resolve().then(() => {
console.log('Promise inside readFile');
});
});
// 微任务
process.nextTick(() => {
console.log('NextTick');
});
这个例子展示了不同阶段和微任务的执行顺序。
阶段与性能优化
理解事件循环阶段有助于性能优化。例如,CPU 密集型任务应该分解为小块,使用 setImmediate
或 process.nextTick
让事件循环继续处理其他任务。
function processChunk(data, chunkSize, callback) {
let index = 0;
function next() {
const chunk = data.slice(index, index + chunkSize);
// 处理数据块...
index += chunkSize;
if (index < data.length) {
// 使用 setImmediate 让事件循环继续
setImmediate(next);
} else {
callback();
}
}
next();
}
阶段与错误处理
不同阶段的错误处理方式可能不同。例如,Promise 拒绝应该在微任务阶段处理,而普通的回调错误可能在轮询阶段处理。
// 微任务错误
Promise.reject(new Error('Promise error')).catch(err => {
console.error('Caught promise error:', err);
});
// 普通回调错误
fs.readFile('nonexistent.txt', (err) => {
if (err) {
console.error('Caught fs error:', err);
}
});
浏览器与Node.js事件循环差异
浏览器的事件循环模型与Node.js有所不同。例如,浏览器没有setImmediate
,但有requestAnimationFrame
等特定于渲染的API。
// 浏览器中的代码
requestAnimationFrame(() => {
console.log('Animation frame');
});
// Node.js中没有requestAnimationFrame
阶段监控与调试
可以使用perf_hooks
模块监控事件循环阶段:
const { monitorEventLoopDelay } = require('perf_hooks');
const histogram = monitorEventLoopDelay();
histogram.enable();
setInterval(() => {
console.log(`Event loop delay: ${histogram.mean} ms`);
histogram.reset();
}, 1000);
这个工具可以帮助识别事件循环中的延迟问题。
阶段与集群
在集群模式下,每个工作进程都有自己的事件循环。理解这一点对于设计可扩展的Node.js应用很重要。
const cluster = require('cluster');
const http = require('http');
if (cluster.isMaster) {
// 主进程
for (let i = 0; i < 4; i++) {
cluster.fork();
}
} else {
// 工作进程
http.createServer((req, res) => {
res.end('Hello from worker');
}).listen(8080);
}
每个工作进程独立处理请求,有自己独立的事件循环。
阶段与子进程
子进程也运行在独立的事件循环中。父进程和子进程通过IPC通信。
const { fork } = require('child_process');
const child = fork('child.js');
child.on('message', (msg) => {
console.log('Message from child:', msg);
});
child.send({ hello: 'world' });
阶段与Worker Threads
Worker Threads提供了真正的多线程能力,每个线程有独立的事件循环。
const { Worker } = require('worker_threads');
const worker = new Worker(`
const { parentPort } = require('worker_threads');
parentPort.on('message', (msg) => {
console.log('Worker received:', msg);
parentPort.postMessage('Hello from worker');
});
`, { eval: true });
worker.on('message', (msg) => {
console.log('Main thread received:', msg);
});
worker.postMessage('Hello from main thread');
阶段与异步迭代
Node.js的异步迭代器也依赖于事件循环阶段。
async function processStream(stream) {
for await (const chunk of stream) {
console.log('Processing chunk:', chunk);
// 使用setImmediate让事件循环继续
await new Promise(resolve => setImmediate(resolve));
}
}
阶段与AbortController
现代Node.js版本支持AbortController,可以在不同阶段中断操作。
const controller = new AbortController();
const { signal } = controller;
setTimeout(() => {
controller.abort();
}, 1000);
fetch('https://example.com', { signal })
.then(response => response.text())
.then(text => console.log(text))
.catch(err => {
if (err.name === 'AbortError') {
console.log('Request aborted');
}
});
阶段与性能测量
可以使用perf_hooks
测量不同阶段的性能:
const { performance, PerformanceObserver } = require('perf_hooks');
const obs = new PerformanceObserver((items) => {
console.log(items.getEntries()[0]);
performance.clearMarks();
});
obs.observe({ entryTypes: ['measure'] });
performance.mark('A');
setImmediate(() => {
performance.mark('B');
performance.measure('A to B', 'A', 'B');
});
阶段与资源限制
Node.js允许设置事件循环延迟的监控和资源限制:
const { ResourceUsage } = require('process');
setInterval(() => {
const usage = process.resourceUsage();
console.log('User CPU time:', usage.ru_utime);
console.log('System CPU time:', usage.ru_stime);
}, 1000);
阶段与诊断报告
Node.js的诊断报告可以帮助分析事件循环问题:
process.on('SIGUSR2', () => {
process.report.writeReport();
});
console.log(`Run 'kill -USR2 ${process.pid}' to generate report`);
阶段与Async Hooks
Async Hooks API可以跟踪异步资源的生命周期:
const async_hooks = require('async_hooks');
const hook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId) {
console.log(`Init: ${type} (${asyncId})`);
},
destroy(asyncId) {
console.log(`Destroy: ${asyncId}`);
}
});
hook.enable();
setTimeout(() => {
console.log('Timeout executed');
}, 100);
上一篇: 常用NPM替代工具(yarn/pnpm)
下一篇: 宏任务与微任务