文章目录
-
- Node.js多线程,高并发
-
- [一、Node.js 处理多线程的方式](#一、Node.js 处理多线程的方式)
-
- [1. 核心模块:`worker_threads`(推荐)](#1. 核心模块:
worker_threads(推荐)) - [2. 历史方案:`child_process`(子进程)](#2. 历史方案:
child_process(子进程))
- [1. 核心模块:`worker_threads`(推荐)](#1. 核心模块:
- [二、Node.js 应对高并发的核心策略](#二、Node.js 应对高并发的核心策略)
-
- [1. 优化主线程:避免阻塞](#1. 优化主线程:避免阻塞)
- [2. 进程集群:`cluster` 模块(利用多核 CPU)](#2. 进程集群:
cluster模块(利用多核 CPU)) - [3. 异步编程规范:Promise/async-await](#3. 异步编程规范:Promise/async-await)
- [4. 高并发进阶方案](#4. 高并发进阶方案)
- 总结
- [Node.js 中多线程(worker_threads)处理实例](#Node.js 中多线程(worker_threads)处理实例)
-
- [一、Node.js 多线程核心适用场景](#一、Node.js 多线程核心适用场景)
-
- [场景 1:复杂数学计算/大数据量运算](#场景 1:复杂数学计算/大数据量运算)
- [场景 2:数据加密/解密(如加解密、哈希计算)](#场景 2:数据加密/解密(如加解密、哈希计算))
- [场景 3:数据解析/转换(如大 JSON、CSV 处理)](#场景 3:数据解析/转换(如大 JSON、CSV 处理))
- [场景 4:图片/视频处理(如裁剪、压缩、格式转换)](#场景 4:图片/视频处理(如裁剪、压缩、格式转换))
- [场景 5:AI/机器学习推理(如 TensorFlow.js 模型预测)](#场景 5:AI/机器学习推理(如 TensorFlow.js 模型预测))
- 二、注意事项(避免滥用多线程)
- 总结
- [思考(Node.js 中的 worker_threads 和浏览器端 JavaScript 的 Web Workers区别)](#思考(Node.js 中的 worker_threads 和浏览器端 JavaScript 的 Web Workers区别))
-
- 一、核心共性:解决的问题一致
- 二、关键差异:适用场景与能力不同
- 三、代码示例对比:直观看差异
-
- [1. 浏览器 Web Workers 示例](#1. 浏览器 Web Workers 示例)
- [2. Node.js worker_threads 示例(简化版)](#2. Node.js worker_threads 示例(简化版))
- 总结
Node.js多线程,高并发
一、Node.js 处理多线程的方式
首先要明确:Node.js 主线程是单线程的(基于 V8 引擎),但它并非完全不支持多线程,而是通过内置模块和生态工具实现多线程能力,核心用于解决 CPU 密集型任务阻塞主线程的问题。
1. 核心模块:worker_threads(推荐)
Node.js v10.5.0 正式引入,是处理多线程的官方标准方案,专门用于创建工作线程,实现主线程与工作线程的通信、数据共享。
示例代码(CPU 密集型任务拆分):
javascript
// 主线程 main.js
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
// 判断是否为主线程
if (isMainThread) {
// 主线程:创建 2 个工作线程处理计算任务
const worker1 = new Worker(__filename, { workerData: { num: 1000000000 } });
const worker2 = new Worker(__filename, { workerData: { num: 2000000000 } });
// 监听工作线程的消息
worker1.on('message', (result) => {
console.log('线程1计算结果:', result);
});
worker2.on('message', (result) => {
console.log('线程2计算结果:', result);
});
// 监听错误
worker1.on('error', (err) => console.error('线程1错误:', err));
worker2.on('error', (err) => console.error('线程2错误:', err));
} else {
// 工作线程:执行 CPU 密集型计算
const { num } = workerData;
let sum = 0;
for (let i = 0; i < num; i++) {
sum += i;
}
// 向主线程返回结果
parentPort.postMessage(sum);
}
关键说明:
isMainThread:区分主线程/工作线程;workerData:主线程向工作线程传递初始化数据;parentPort.postMessage():工作线程向主线程发送消息;- 工作线程有独立的 V8 实例和内存空间,避免阻塞主线程的事件循环。
2. 历史方案:child_process(子进程)
比 worker_threads 更早的多进程方案(Node.js 早期核心模块),通过创建独立子进程实现并行,适合跨语言调用或独立进程任务:
javascript
// main.js
const { spawn } = require('child_process');
// 创建子进程执行 node 脚本
const child = spawn('node', ['child.js']);
// 监听子进程输出
child.stdout.on('data', (data) => {
console.log('子进程输出:', data.toString());
});
二、Node.js 应对高并发的核心策略
Node.js 高并发的核心优势是非阻塞 I/O + 事件循环(主线程处理 I/O 时不等待,通过回调/异步函数处理结果),但需结合以下策略最大化并发能力:
1. 优化主线程:避免阻塞
- 所有 CPU 密集型任务(如大计算、加密)移交
worker_threads/子进程; - 避免同步 I/O(如
fs.readFileSync),全部使用异步 API(fs.readFile/promises)。
2. 进程集群:cluster 模块(利用多核 CPU)
Node.js 主线程单线程无法利用多核,cluster 模块可创建多个子进程(每个进程绑定一个 CPU 核心),共享同一个端口,实现多核并发:
javascript
// server.js
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length; // 获取 CPU 核心数
if (cluster.isPrimary) {
// 主进程:创建子进程(数量 = CPU 核心数)
console.log(`主进程 ${process.pid} 启动`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// 子进程退出时重启(保证可用性)
cluster.on('exit', (worker, code, signal) => {
console.log(`子进程 ${worker.process.pid} 退出,重启中...`);
cluster.fork();
});
} else {
// 子进程:启动 HTTP 服务(所有子进程共享端口)
http.createServer((req, res) => {
res.writeHead(200);
res.end(`响应来自进程 ${process.pid}\n`);
}).listen(8080);
console.log(`子进程 ${process.pid} 启动,监听 8080 端口`);
}
3. 异步编程规范:Promise/async-await
使用 async/await 替代嵌套回调(回调地狱),提升代码可读性和可维护性,同时保证异步流程不阻塞:
javascript
// 异步处理 I/O 示例
const fs = require('fs/promises');
async function readFiles() {
try {
// 并行读取文件(非阻塞)
const [file1, file2] = await Promise.all([
fs.readFile('file1.txt', 'utf8'),
fs.readFile('file2.txt', 'utf8')
]);
console.log('文件1:', file1);
console.log('文件2:', file2);
} catch (err) {
console.error('读取文件失败:', err);
}
}
readFiles();
4. 高并发进阶方案
- 使用高性能 Web 框架:如 Express → Koa → Fastify(Fastify 性能远超 Express,适合高并发);
- 缓存层:Redis 缓存热点数据(减少数据库 I/O,提升响应速度);
- 反向代理/负载均衡:Nginx 前置代理,分发请求到多个 Node.js 进程/实例;
- 限流/熔断 :使用
express-rate-limit、opossum等库,防止服务被高并发压垮; - 数据库优化 :使用连接池(如
mysql2/promise的连接池),避免频繁创建/销毁数据库连接。
总结
- 多线程处理 :Node.js 主线程单线程,通过
worker_threads(轻量级多线程)、child_process(子进程)处理 CPU 密集型任务,避免阻塞主线程; - 高并发核心 :依托非阻塞 I/O + 事件循环,结合
cluster模块利用多核 CPU,配合缓存、限流、负载均衡等策略最大化并发能力; - 关键原则:主线程只处理 I/O 密集型任务(网络、文件、数据库),CPU 密集型任务移交线程/进程,避免阻塞事件循环。
Node.js 中多线程(worker_threads)处理实例
你希望我列举 Node.js 中需要用多线程(worker_threads)处理的具体场景,并搭配可运行的实例代码,这能帮你更直观地判断何时该引入多线程,避免主线程阻塞。
一、Node.js 多线程核心适用场景
Node.js 主线程是单线程 + 事件循环,所有 CPU 密集型任务都适合用多线程处理(I/O 密集型任务无需多线程,异步 API 即可)。以下是最常见的场景及实例:
场景 1:复杂数学计算/大数据量运算
适用原因 :循环、矩阵运算、大数累加等会长时间占用 CPU,阻塞主线程的事件循环,导致 HTTP 服务、定时器等无法响应。
实例代码:
javascript
// main.js(主线程)
const { Worker } = require('worker_threads');
const http = require('http');
// 启动 HTTP 服务(模拟主线程处理业务)
const server = http.createServer((req, res) => {
if (req.url === '/calculate') {
// 创建工作线程处理大数计算
const calcWorker = new Worker('./calc-worker.js', {
workerData: { max: 1000000000 } // 传递计算参数
});
// 接收工作线程的结果
calcWorker.on('message', (sum) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ sum, pid: process.pid }));
});
// 监听工作线程错误
calcWorker.on('error', (err) => {
res.writeHead(500);
res.end(`计算错误: ${err.message}`);
});
} else {
res.end('主线程正常响应,访问 /calculate 触发计算');
}
}).listen(3000, () => {
console.log('服务启动:http://localhost:3000');
});
// calc-worker.js(工作线程)
const { parentPort, workerData } = require('worker_threads');
const { max } = workerData;
// 大数累加(CPU 密集型)
let sum = 0;
for (let i = 0; i < max; i++) {
sum += i;
}
// 向主线程返回结果
parentPort.postMessage(sum);
测试效果:
- 启动服务后,访问
http://localhost:3000能立即响应(主线程未阻塞); - 访问
http://localhost:3000/calculate时,计算任务在工作线程执行,主线程仍能处理其他请求。
场景 2:数据加密/解密(如加解密、哈希计算)
适用原因 :MD5、SHA256、AES 加解密(尤其是大文件/大数据)会占用大量 CPU 资源,阻塞主线程。
实例代码:
javascript
// main.js(主线程)
const { Worker } = require('worker_threads');
const fs = require('fs/promises');
async function encryptLargeFile(filePath) {
// 读取文件内容(异步 I/O,不阻塞主线程)
const fileContent = await fs.readFile(filePath);
// 创建工作线程处理加密
const encryptWorker = new Worker('./encrypt-worker.js');
encryptWorker.postMessage({ data: fileContent });
// 接收加密结果
encryptWorker.on('message', (encryptedData) => {
console.log('加密完成,密文长度:', encryptedData.length);
// 保存加密结果
fs.writeFile(`${filePath}.encrypted`, encryptedData);
});
}
// 测试:加密一个大文件(如 100MB 的日志文件)
encryptLargeFile('./large-file.txt');
// encrypt-worker.js(工作线程)
const { parentPort } = require('worker_threads');
const crypto = require('crypto');
// 监听主线程的加密请求
parentPort.on('message', ({ data }) => {
// AES-256 加密(CPU 密集型)
const cipher = crypto.createCipheriv(
'aes-256-cbc',
crypto.scryptSync('my-secret-key', 'salt', 32), // 密钥
Buffer.alloc(16, 'iv-1234567890123456') // 向量
);
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
// 返回加密结果
parentPort.postMessage(encrypted);
});
场景 3:数据解析/转换(如大 JSON、CSV 处理)
适用原因 :解析 GB 级别的 JSON/CSV 文件、大数据格式转换(如 JSON → Excel)会持续占用 CPU,导致主线程卡死。
实例代码:
javascript
// main.js(主线程)
const { Worker } = require('worker_threads');
const fs = require('fs');
// 读取大 CSV 文件(流式读取,避免内存溢出)
const csvStream = fs.createReadStream('./large-data.csv', 'utf8');
// 创建工作线程处理 CSV → JSON 转换
const parseWorker = new Worker('./parse-worker.js');
// 分块向工作线程发送 CSV 数据
csvStream.on('data', (chunk) => {
parseWorker.postMessage({ type: 'chunk', data: chunk });
});
// 通知工作线程数据发送完成
csvStream.on('end', () => {
parseWorker.postMessage({ type: 'end' });
});
// 接收转换后的 JSON 结果
parseWorker.on('message', (jsonData) => {
console.log('转换完成,JSON 数据条数:', jsonData.length);
fs.writeFile('./converted-data.json', JSON.stringify(jsonData));
});
// parse-worker.js(工作线程)
const { parentPort } = require('worker_threads');
const csvParser = require('csv-parser'); // 需安装:npm i csv-parser
const { Writable } = require('stream');
let results = [];
// 处理主线程发送的 CSV 分块
parentPort.on('message', (msg) => {
if (msg.type === 'chunk') {
// 解析 CSV 分块(CPU 密集型)
const parser = csvParser();
parser.on('data', (data) => results.push(data));
// 写入分块数据到解析器
parser.write(msg.data);
} else if (msg.type === 'end') {
// 解析完成,返回结果
parentPort.postMessage(results);
}
});
场景 4:图片/视频处理(如裁剪、压缩、格式转换)
适用原因 :使用 sharp、ffmpeg 等库处理图片/视频时,编码/解码会占用大量 CPU,阻塞主线程。
实例代码:
javascript
// main.js(主线程)
const { Worker } = require('worker_threads');
const fs = require('fs/promises');
async function processImages(imagePaths) {
// 批量处理图片,每个图片创建一个工作线程(或复用线程池)
for (const path of imagePaths) {
const imageWorker = new Worker('./image-worker.js', {
workerData: { inputPath: path, outputPath: `${path}-compressed.jpg` }
});
imageWorker.on('message', (status) => {
console.log(`图片处理完成: ${status}`);
});
}
}
// 测试:处理多个图片
processImages(['./img1.jpg', './img2.jpg', './img3.jpg']);
// image-worker.js(工作线程)
const { parentPort, workerData } = require('worker_threads');
const sharp = require('sharp'); // 需安装:npm i sharp
const { inputPath, outputPath } = workerData;
// 图片压缩 + 裁剪(CPU 密集型)
sharp(inputPath)
.resize(800, 600) // 裁剪尺寸
.jpeg({ quality: 60 }) // 压缩质量
.toFile(outputPath)
.then(() => {
parentPort.postMessage(outputPath);
})
.catch((err) => {
parentPort.postMessage(`失败: ${err.message}`);
});
场景 5:AI/机器学习推理(如 TensorFlow.js 模型预测)
适用原因 :TensorFlow.js 运行模型推理时,大量张量计算会占用 CPU,阻塞主线程。
实例代码:
javascript
// main.js(主线程)
const { Worker } = require('worker_threads');
// 创建工作线程执行模型推理
const aiWorker = new Worker('./ai-worker.js', {
workerData: { input: [1.2, 3.4, 5.6, 7.8] } // 推理输入数据
});
aiWorker.on('message', (prediction) => {
console.log('模型推理结果:', prediction);
});
// ai-worker.js(工作线程)
const { parentPort, workerData } = require('worker_threads');
const tf = require('@tensorflow/tfjs-node'); // 需安装:npm i @tensorflow/tfjs-node
// 加载预训练模型(示例:简单回归模型)
async function runInference() {
// 模拟创建简单模型
const model = tf.sequential();
model.add(tf.layers.dense({ units: 1, inputShape: [4] }));
model.compile({ optimizer: 'sgd', loss: 'meanSquaredError' });
// 执行推理(CPU 密集型)
const input = tf.tensor2d([workerData.input]);
const prediction = model.predict(input);
const result = await prediction.data();
// 返回结果
parentPort.postMessage(result[0]);
}
runInference();
二、注意事项(避免滥用多线程)
- 线程池复用 :不要为每个小任务创建新线程(创建/销毁线程有开销),可使用
piscina等库实现线程池; - 数据传输成本 :主线程与工作线程通过
postMessage传递数据(结构化克隆),大对象传输会有开销,尽量传递小数据/共享内存; - I/O 密集型任务无需多线程:如数据库查询、网络请求,用 Node.js 异步 API 即可,多线程反而增加复杂度。
总结
- 核心适用场景 :所有CPU 密集型任务 (数学计算、加解密、数据解析、图片/视频处理、AI 推理)都适合用
worker_threads处理,避免阻塞主线程; - 实例核心逻辑 :主线程负责接收请求/分发任务,工作线程专注处理 CPU 密集型计算,通过
postMessage通信; - 关键原则:多线程只解决 CPU 阻塞问题,I/O 密集型任务优先用异步 API,避免滥用多线程增加复杂度。
思考(Node.js 中的 worker_threads 和浏览器端 JavaScript 的 Web Workers区别)
你想知道 Node.js 中的 worker_threads 和浏览器端 JavaScript 的 Web Workers 作用是否一致,这个问题抓得很准,二者核心目标相似但细节差异很大,是新手容易混淆的点。
一、核心共性:解决的问题一致
worker_threads 和 Web Workers 的核心作用是完全相同的:
- 都是为了解决 JavaScript 单线程模型的痛点------将 CPU 密集型任务(如复杂计算、数据处理)从主线程中剥离,放到独立的线程/工作单元中执行;
- 都能避免主线程被阻塞(Node.js 主线程阻塞会导致事件循环卡住,浏览器主线程阻塞会导致页面卡顿/无响应);
- 都遵循「主线程 ↔ 工作线程」的消息通信机制(通过
postMessage传递数据,不能直接共享内存/变量)。
简单说:二者都是为了让 JavaScript 突破单线程限制,实现并行处理 CPU 密集型任务。
二、关键差异:适用场景与能力不同
虽然核心目标一致,但因运行环境(Node.js vs 浏览器)不同,二者在设计、API、能力上有显著区别:
| 特性 | Node.js worker_threads |
浏览器 Web Workers |
|---|---|---|
| 运行环境 | Node.js 服务端 | 浏览器客户端 |
| 核心 API 差异 | 提供 workerData(初始化传参)、isMainThread、MessageChannel、共享内存(SharedArrayBuffer)等 |
核心 API 为 Worker、SharedWorker,无 workerData,传参只能通过 postMessage |
| 资源访问权限 | 可访问文件系统、网络、数据库、进程等 Node.js 核心能力 | 权限受限:不能访问 DOM、localStorage(部分),只能做计算/网络请求 |
| 线程类型 | 仅普通工作线程 | 分 DedicatedWorker(专属)、SharedWorker(共享)、ServiceWorker(离线缓存) |
| 内存共享 | 支持 SharedArrayBuffer + Atomics 实现内存共享 |
同样支持 SharedArrayBuffer,但受浏览器安全策略限制 |
| 错误处理 | 可监听 error、exit 事件 |
监听 error、messageerror 事件 |
三、代码示例对比:直观看差异
1. 浏览器 Web Workers 示例
javascript
// 主线程(页面 JS)
const worker = new Worker('worker.js'); // 加载独立的 worker 脚本
// 发送消息给 worker
worker.postMessage({ num: 1000000 });
// 接收 worker 结果
worker.onmessage = (e) => {
console.log('计算结果:', e.data);
};
// worker.js(独立文件,不能访问 DOM)
self.onmessage = (e) => {
const { num } = e.data;
let sum = 0;
for (let i = 0; i < num; i++) sum += i;
self.postMessage(sum); // 向主线程返回结果
};
2. Node.js worker_threads 示例(简化版)
javascript
// main.js(主线程)
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
if (isMainThread) {
// 主线程:创建 worker,直接传初始化数据(无需单独文件,可复用当前文件)
const worker = new Worker(__filename, { workerData: { num: 1000000 } });
worker.on('message', (result) => {
console.log('计算结果:', result);
});
} else {
// 工作线程:直接读取 workerData,无需等待 postMessage
const { num } = workerData;
let sum = 0;
for (let i = 0; i < num; i++) sum += i;
parentPort.postMessage(sum);
}
总结
- 核心作用一致:二者都是为了把 CPU 密集型任务从主线程剥离,避免阻塞,实现 JavaScript 的并行处理;
- 核心差异在环境 :
worker_threads是 Node.js 服务端方案,可访问系统资源;Web Workers是浏览器客户端方案,权限受限,且有专属/共享 worker 等细分类型; - API 细节不同 :
worker_threads提供workerData等更便捷的传参方式,而Web Workers只能通过postMessage初始化传参。