线程
在Node.js中,线程是实现高性能和并发的关键概念。由于Node.js主线程是单线程的,理解其线程模型和使用场景对编写高效代码至关重要。以下从基础到高级全面解析:
一、Node.js线程模型概述
Node.js采用单线程事件循环 + 后台线程池的混合模型:
-
主线程(事件循环)
- 执行JavaScript代码和处理事件循环的六个阶段(timers → pending callbacks → poll → check → close callbacks)。
- 单线程执行,若被CPU密集型任务阻塞,会导致所有异步操作暂停。
-
libuv线程池
- 处理无法异步化的I/O操作(如文件读写、DNS解析)和用户提交的CPU密集型任务。
- 默认大小为4个线程,可通过
UV_THREADPOOL_SIZE
环境变量调整。
二、libuv线程池的使用场景
线程池自动处理以下操作:
- 文件系统操作:如
fs.readFile
、fs.writeFile
。 - DNS解析:如
dns.lookup
。 - 加密操作:如
crypto.pbkdf2
。
示例:文件读取(自动使用线程池)
const fs = require('fs');
// 异步读取文件(在线程池中执行)
fs.readFile('large_file.txt', (err, data) => {
if (err) throw err;
console.log('文件读取完成');
});
// 主线程继续执行其他代码
console.log('继续执行...');
三、Worker Threads(工作线程)
Node.js 10+引入的官方多线程API,用于处理CPU密集型任务:
1. 基本用法
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
if (isMainThread) {
// 主线程创建工作线程
const worker = new Worker(__filename, {
workerData: { num: 10 }
});
worker.on('message', (result) => {
console.log('主线程收到结果:', result); // 输出: 55
});
worker.on('error', (err) => {
console.error('工作线程错误:', err);
});
worker.on('exit', (code) => {
console.log(`工作线程退出,退出码: ${code}`);
});
} else {
// 工作线程执行计算
const { num } = workerData;
let sum = 0;
for (let i = 1; i <= num; i++) {
sum += i;
}
// 向主线程发送结果
parentPort.postMessage(sum);
}
2. 多线程计算示例
将大计算任务拆分到多个线程:
const { Worker } = require('worker_threads');
function calculatePrimesInRange(start, end) {
return new Promise((resolve, reject) => {
const worker = new Worker(`
const { parentPort } = require('worker_threads');
function isPrime(num) {
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) return false;
}
return num > 1;
}
const primes = [];
for (let i = ${start}; i <= ${end}; i++) {
if (isPrime(i)) primes.push(i);
}
parentPort.postMessage(primes);
`, { eval: true });
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`));
});
});
}
// 并行计算多个范围的素数
async function findPrimes() {
const [primes1, primes2, primes3, primes4] = await Promise.all([
calculatePrimesInRange(2, 100000),
calculatePrimesInRange(100001, 200000),
calculatePrimesInRange(200001, 300000),
calculatePrimesInRange(300001, 400000)
]);
const allPrimes = [...primes1, ...primes2, ...primes3, ...primes4];
console.log(`找到 ${allPrimes.length} 个素数`);
}
findPrimes();
四、线程池 vs. Worker Threads
特性 | libuv线程池 | Worker Threads |
---|---|---|
创建方式 | 自动创建,默认4个线程 | 手动创建,数量不限 |
适用场景 | I/O密集型操作(文件读写、DNS解析) | CPU密集型计算(加密、大数据处理) |
API复杂度 | 透明使用(无需手动管理) | 需要手动创建和管理线程 |
数据传递 | 不支持直接传递 | 通过postMessage 传递(拷贝而非共享) |
内存隔离 | 不隔离(共享主线程内存) | 隔离(每个线程有独立V8实例) |
五、多线程最佳实践
-
避免阻塞主线程
- CPU密集型任务必须使用Worker Threads,否则会阻塞事件循环:
// 错误示例:阻塞主线程
function heavyCalculation() {
let result = 0;
for (let i = 0; i < 1e9; i++) {
result += i;
}
return result;
}
// 正确做法:使用Worker Threads
const worker = new Worker(`
const { parentPort } = require('worker_threads');
let result = 0;
for (let i = 0; i < 1e9; i++) {
result += i;
}
parentPort.postMessage(result);
`, { eval: true });
- CPU密集型任务必须使用Worker Threads,否则会阻塞事件循环:
-
优化线程池大小
- 根据应用特性调整线程池大小:
# 设置线程池为16个线程
UV_THREADPOOL_SIZE=16 node app.js
- 根据应用特性调整线程池大小:
-
线程间通信
- 使用
postMessage
传递数据,避免共享状态:// 主线程
worker.postMessage({ command: 'process', data: largeArray });
// 工作线程
parentPort.on('message', (msg) => {
if (msg.command === 'process') {
// 处理数据
parentPort.postMessage(processData(msg.data));
}
});
- 使用
-
错误处理
- 监听
error
和exit
事件,确保线程异常退出时能捕获:worker.on('error', (err) => {
console.error('工作线程错误:', err);
});
worker.on('exit', (code) => {
if (code !== 0) {
console.error(`工作线程异常退出,退出码: ${code}`);
// 可选择重启线程
}
});
- 监听
六、常见误区与注意事项
-
认为Node.js完全单线程
- Node.js主线程是单线程的,但libuv线程池和Worker Threads允许并行执行。
-
过度使用线程
- 创建过多线程会导致上下文切换开销,降低性能。线程数应与CPU核心数匹配。
-
共享状态风险
- Worker Threads间不共享内存,但主线程与Worker可通过
SharedArrayBuffer
共享内存(需谨慎使用,避免竞态条件):// 创建共享内存
const sab = new SharedArrayBuffer(1024);
const uint8Array = new Uint8Array(sab);
// 传递给工作线程
const worker = new Worker(__filename, { workerData: sab });
- Worker Threads间不共享内存,但主线程与Worker可通过
通过合理利用线程池和Worker Threads,Node.js能同时处理高并发I/O和CPU密集型任务,充分发挥多核CPU的性能。