单线程与事件循环
单线程的本质
Node.js 采用单线程模型处理 JavaScript 代码执行。这个设计意味着主线程在同一时刻只能执行一个任务。与多线程环境不同,它不需要处理线程同步、锁竞争等复杂问题。单线程的核心优势在于简化了并发模型,但也带来了潜在的性能瓶颈。
function computeIntensiveTask() {
let sum = 0;
for (let i = 0; i < 1e9; i++) {
sum += i;
}
return sum;
}
console.log('Start');
computeIntensiveTask(); // 阻塞主线程
console.log('End'); // 长时间延迟后才会执行
事件循环机制
事件循环是 Node.js 实现非阻塞 I/O 的关键。它本质上是一个无限循环,不断检查事件队列并执行回调。整个机制分为多个阶段,每个阶段处理特定类型的任务:
- 定时器阶段:执行 setTimeout 和 setInterval 回调
- I/O 回调阶段:处理网络、文件等 I/O 事件
- 闲置/准备阶段:内部使用
- 轮询阶段:检索新的 I/O 事件
- 检查阶段:执行 setImmediate 回调
- 关闭回调阶段:处理如 socket.on('close') 等事件
setTimeout(() => console.log('Timeout'), 0);
setImmediate(() => console.log('Immediate'));
// 输出顺序可能不同,取决于事件循环的启动时间
非阻塞 I/O 实现
Node.js 通过 libuv 库实现真正的异步 I/O。当遇到 I/O 操作时,主线程会将操作委托给系统内核,然后继续执行后续代码。内核完成操作后,将结果放入事件队列,等待事件循环处理。
const fs = require('fs');
console.log('开始读取文件');
fs.readFile('largefile.txt', (err, data) => {
console.log('文件读取完成');
});
console.log('继续执行其他任务');
// 输出顺序:
// 开始读取文件
// 继续执行其他任务
// 文件读取完成
微任务与宏任务
事件循环处理的任务分为两种优先级:
- 微任务:process.nextTick、Promise 回调
- 宏任务:setTimeout、setInterval、I/O 操作
微任务在当前阶段结束后立即执行,优先级高于宏任务。
Promise.resolve().then(() => console.log('Promise'));
process.nextTick(() => console.log('nextTick'));
setTimeout(() => console.log('Timeout'), 0);
// 输出顺序:
// nextTick
// Promise
// Timeout
常见的性能陷阱
单线程模型下,CPU 密集型任务会阻塞事件循环:
// 错误示例:同步加密大文件
const crypto = require('crypto');
function encryptLargeFile() {
const data = Buffer.alloc(1e8); // 100MB 数据
return crypto.createHash('sha256').update(data).digest('hex');
}
// 正确做法:使用流式处理或工作线程
const { Worker } = require('worker_threads');
function asyncEncrypt(filePath) {
return new Promise((resolve) => {
const worker = new Worker('./encrypt-worker.js', { workerData: filePath });
worker.on('message', resolve);
});
}
事件循环的监控
Node.js 提供了性能监控接口,可以检测事件循环延迟:
const { monitorEventLoopDelay } = require('perf_hooks');
const histogram = monitorEventLoopDelay();
histogram.enable();
setInterval(() => {
console.log(`事件循环延迟(ms):
p50=${histogram.percentile(50)/1e6},
p99=${histogram.percentile(99)/1e6}`);
histogram.reset();
}, 1000);
实际应用优化
Web 服务器中的最佳实践:
const express = require('express');
const app = express();
// 错误处理中间件应该声明在最前面
app.use((err, req, res, next) => {
console.error(err);
res.status(500).send('Server Error');
});
// CPU 密集型路由应该使用工作线程
app.get('/compute', async (req, res) => {
const result = await runInWorker('./compute.js');
res.json({ result });
});
// I/O 密集型路由可以直接处理
app.get('/data', (req, res) => {
db.query('SELECT * FROM large_table', (err, data) => {
if (err) return next(err);
res.json(data);
});
});
高级模式应用
利用事件循环特性实现流量控制:
class RateLimiter {
constructor(limit) {
this.queue = [];
this.active = 0;
this.limit = limit;
}
async execute(task) {
if (this.active >= this.limit) {
await new Promise(resolve => this.queue.push(resolve));
}
this.active++;
try {
return await task();
} finally {
this.active--;
if (this.queue.length) {
this.queue.shift()();
}
}
}
}
// 使用示例
const limiter = new RateLimiter(5);
for (let i = 0; i < 100; i++) {
limiter.execute(() => fetch('https://api.example.com/data/' + i));
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:非阻塞I/O模型
下一篇:CommonJS模块系统