您现在的位置是:网站首页 > 单线程与事件循环文章详情
单线程与事件循环
陈川
【
Node.js
】
30536人已围观
3594字
单线程的本质
Node.js采用单线程模型处理JavaScript代码执行。这里的单线程指的是主事件循环线程,它负责执行所有JavaScript代码。这种设计带来几个关键特性:
- 任何时候只有一个JavaScript指令在执行
- 阻塞操作会冻结整个应用
- 非阻塞I/O操作是性能关键
// 这段代码会阻塞事件循环
function blockingOperation() {
const start = Date.now();
while (Date.now() - start < 5000) {
// 模拟5秒的CPU密集型任务
}
console.log('阻塞操作完成');
}
blockingOperation(); // 这期间其他请求无法处理
console.log('这段代码要等5秒后才能执行');
事件循环的架构
Node.js的事件循环由libuv库实现,包含多个阶段:
- 定时器阶段:执行setTimeout和setInterval回调
- 待定回调阶段:执行系统操作回调如TCP错误
- 闲置/准备阶段:内部使用
- 轮询阶段:检索新的I/O事件
- 检查阶段:执行setImmediate回调
- 关闭回调阶段:处理关闭事件如socket.on('close')
// 演示事件循环各阶段执行顺序
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// 输出顺序可能不同,取决于事件循环启动时间
非阻塞I/O的实现
Node.js通过以下机制实现非阻塞I/O:
- 系统内核提供的非阻塞系统调用(如epoll/kqueue)
- 线程池处理无法异步的系统调用(如文件I/O)
- 回调函数处理完成事件
const fs = require('fs');
// 非阻塞文件读取
fs.readFile('/path/to/file', (err, data) => {
if (err) throw err;
console.log(data);
});
// 这段代码会立即执行
console.log('文件读取已发起,继续执行其他代码');
事件循环与性能优化
理解事件循环有助于性能优化:
- 避免在回调中执行CPU密集型任务
- 合理使用process.nextTick和setImmediate
- 分解长时间任务为多个小任务
// 分解大任务示例
function processChunk(start, end) {
// 处理数据块
}
function processLargeArray(array) {
let index = 0;
const chunkSize = 1000;
function nextChunk() {
const chunk = array.slice(index, index + chunkSize);
processChunk(chunk);
index += chunkSize;
if (index < array.length) {
setImmediate(nextChunk); // 让事件循环处理其他事件
}
}
nextChunk();
}
常见的陷阱与解决方案
开发者常遇到的事件循环问题:
- 回调地狱:使用Promise/async-await解决
- 未捕获异常:使用domain或process.on('uncaughtException')
- 事件循环阻塞:使用工作线程或子进程
// 回调地狱示例与改进
// 旧方式
fs.readFile('file1', (err, data1) => {
fs.readFile('file2', (err, data2) => {
fs.writeFile('output', data1 + data2, (err) => {
// 更多嵌套...
});
});
});
// 使用async/await改进
async function processFiles() {
try {
const data1 = await fs.promises.readFile('file1');
const data2 = await fs.promises.readFile('file2');
await fs.promises.writeFile('output', data1 + data2);
} catch (err) {
console.error(err);
}
}
微任务与宏任务
JavaScript执行分为两种任务队列:
- 微任务:Promise回调、process.nextTick
- 宏任务:setTimeout、setInterval、setImmediate、I/O
// 执行顺序示例
console.log('脚本开始');
setTimeout(() => console.log('setTimeout'), 0);
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
console.log('脚本结束');
/*
典型输出顺序:
脚本开始
脚本结束
nextTick
promise
setTimeout
*/
工作线程与事件循环
Node.js的工作线程机制:
- 每个工作线程有自己的事件循环
- 主线程和工作线程通过消息传递通信
- 适合CPU密集型任务
const { Worker } = require('worker_threads');
// 主线程
const worker = new Worker(`
const { parentPort } = require('worker_threads');
parentPort.on('message', (msg) => {
// 执行CPU密集型任务
parentPort.postMessage(result);
});
`);
worker.on('message', (result) => {
console.log('收到工作线程结果:', result);
});
worker.postMessage('开始计算');
事件循环在实际应用中的表现
不同场景下事件循环的行为差异:
- HTTP服务器:每个请求都是事件循环中的回调
- 流处理:数据到达时触发可读事件
- 数据库操作:查询完成时触发回调
const http = require('http');
// HTTP服务器示例
const server = http.createServer((req, res) => {
// 这个回调在事件循环中执行
if (req.url === '/compute') {
// 模拟CPU密集型任务
let sum = 0;
for (let i = 0; i < 1e9; i++) {
sum += i;
}
res.end(`Sum is ${sum}`);
} else {
res.end('OK');
}
});
server.listen(3000, () => {
console.log('服务器运行中');
});
// 访问/compute会阻塞所有其他请求
上一篇: 非阻塞I/O模型
下一篇: CommonJS模块系统