介绍:JavaScript 多线程的迷思与现实
JavaScript 传统上与单线程执行模型相关联。许多开发者,尤其是来自 C++ 或 Java 世界的开发者,认为这是一个限制。然而,现代 Node.js 生态系统提供了强大的工具来处理并发性,更重要的是,处理真正的多线程。在本文中,我们将解析 Event Loop 如何工作,何时使用 Worker Threads,以及如何正确构建高负载应用程序。
与每个线程拥有自己的栈并可以在不同 CPU 核心上并行执行的经典多线程不同,浏览器和 Node.js 中的 JavaScript 使用基于事件的并发模型。但随着 Node.js 中 worker_threads 模块的出现,我们获得了真正并行性的能力。
主体部分
1. Event Loop:Node.js 中并发性的核心
Event Loop 是一种机制,它允许 Node.js 执行非阻塞输入/输出(I/O)操作,尽管 JavaScript 是单线程的。它不断检查任务队列并按顺序执行它们。
Event Loop 的主要阶段:
- Timers — 执行通过
setTimeout()和setInterval()安排的回调。 - I/O callbacks — 处理已完成的输入/输出操作。
- Idle, prepare — Node.js 内部阶段。
- Poll — 获取新的 I/O 事件并执行它们的回调。
- Check — 执行
setImmediate()的回调。 - Close callbacks — 处理关闭事件(例如,
socket.on('close', ...))。
Event Loop 工作示例:
const fs = require('fs');
console.log('程序开始');
setTimeout(() => {
console.log('定时器触发');
}, 0);
setImmediate(() => {
console.log('setImmediate 已执行');
});
fs.readFile(__filename, () => {
console.log('文件已读取');
});
console.log('程序结束');
// 输出:
// 程序开始
// 程序结束
// setImmediate 已执行
// 定时器触发
// 文件已读取
重要的是要理解:Event Loop 并不会使代码变成多线程,它只是有效地在任务之间分配时间。如果一个任务占用 CPU 时间过长(例如,复杂的循环),整个 Event Loop 就会被阻塞。
2. 阻塞问题及解决方案:Worker Threads
对于 CPU 密集型任务(加密、图像处理、大数据解析),Event Loop 并不适用——它会阻塞整个服务器。这时,Worker Threads 就派上了用场。
Worker Threads 是 Node.js 的一个模块,它允许创建真正的操作系统线程。每个 worker 都有自己的 V8 实例和 Event Loop,这使得代码可以并行执行。
使用 Worker Threads 的基本示例:
// main.js
const { Worker } = require('worker_threads');
function runWorker(workerData) {
return new Promise((resolve, reject) => {
const worker = new Worker('./worker.js', { workerData });
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker 以代码 ${code} 停止`));
}
});
});
}
async function main() {
console.log('启动 worker...');
const result = await runWorker(42);
console.log('结果:', result);
}
main().catch(console.error);
// worker.js
const { parentPort, workerData } = require('worker_threads');
// CPU 密集型任务
function fibonacci(n) {
if (n < 2) return n;
return fibonacci(n - 1) + fibonacci(n -