HoRain云--Node.js 多进程
我们都知道 Node.js 是以单线程的模式运行的但它使用的是事件驱动来处理并发这样有助于我们在多核 cpu 的系统上创建多个子进程从而提高性能。每个子进程总是带有三个流对象child.stdin, child.stdout 和child.stderr。他们可能会共享父进程的 stdio 流或者也可以是独立的被导流的流对象。Node 提供了 child_process 与 cluster 模块来创建子进程。child_process模块exec- child_process.exec 使用子进程执行命令缓存子进程的输出并将子进程的输出以回调函数参数的形式返回。execFile- 直接执行可执行文件略安全。spawn- child_process.spawn 使用指定的命令行参数创建新进程。fork- child_process.fork 是 spawn()的特殊形式用于在子进程中运行的模块如 fork(./son.js) 相当于 spawn(node, [./son.js]) 。与spawn方法不同的是fork会在父进程与子进程之间建立一个通信管道用于进程之间的通信。cluster模块在一个端口上多工作进程共享监听实现一核一进程的 HTTP 服务。适合把 Web 服务横向吃满多核。备注官方更推荐新项目仔细评估如直接用反向代理 多进程、或worker_threads做 CPU 密集但cluster依旧广泛可用。exec() 方法child_process.exec 使用子进程执行命令缓存子进程的输出并将子进程的输出以回调函数参数的形式返回。语法如下所示child_process.exec(command[, options], callback)参数参数说明如下command字符串 将要运行的命令参数使用空格隔开options 对象可以是cwd 字符串子进程的当前工作目录env对象 环境变量键值对encoding 字符串字符编码默认 utf8shell 字符串将要执行命令的 Shell默认: 在 UNIX 中为/bin/sh 在 Windows 中为cmd.exe Shell 应当能识别-c开关在 UNIX 中或/s /c在 Windows 中。 在Windows 中命令行解析应当能兼容cmd.exetimeout数字超时时间默认 0maxBuffer数字 在 stdout 或 stderr 中允许存在的最大缓冲二进制如果超出那么子进程将会被杀死 默认: 200*1024killSignal 字符串结束信号默认SIGTERMuid数字设置用户进程的 IDgid数字设置进程组的 IDcallback 回调函数包含三个参数error, stdout 和 stderr。exec() 方法返回最大的缓冲区并等待进程结束一次性返回缓冲区的内容。实例让我们创建两个 js 文件 support.js 和 master.js。support.js 文件代码console.log(进程 process.argv[2] 执行。 );master.js 文件代码const fs require(fs); const child_process require(child_process); for(var i0; i3; i) { var workerProcess child_process.exec(node support.js i, function (error, stdout, stderr) { if (error) { console.log(error.stack); console.log(Error code: error.code); console.log(Signal received: error.signal); } console.log(stdout: stdout); console.log(stderr: stderr); }); workerProcess.on(exit, function (code) { console.log(子进程已退出退出码 code); }); }执行以上代码输出结果为$ node master.js 子进程已退出退出码 0 stdout: 进程 1 执行。 stderr: 子进程已退出退出码 0 stdout: 进程 0 执行。 stderr: 子进程已退出退出码 0 stdout: 进程 2 执行。 stderr:解析exec会启动3 个子进程分别执行node support.js 0node support.js 1node support.js 2每个子进程运行后立刻输出进程 0 执行。 进程 1 执行。 进程 2 执行。这部分是子进程的标准输出(stdout)会被缓冲等子进程结束时一起传回exec的回调。子进程退出时会先触发exit事件然后exec的回调才会执行注意Node.js 文档里说明exit事件发生在进程结束时而exec的回调是stdio流关闭后触发通常 exit 会先于回调打印。输出顺序是不确定的因为 3 个子进程并行执行谁先结束取决于操作系统调度。spawn() 方法child_process.spawn 使用指定的命令行参数创建新进程语法格式如下child_process.spawn(command[, args][, options])参数参数说明如下command将要运行的命令argsArray 字符串参数数组options Objectcwd String 子进程的当前工作目录env Object 环境变量键值对stdio Array|String 子进程的 stdio 配置detached Boolean 这个子进程将会变成进程组的领导uid Number 设置用户进程的 IDgid Number 设置进程组的 IDspawn() 方法返回流 (stdout stderr)在进程返回大量数据时使用。进程一旦开始执行时 spawn() 就开始接收响应。实例让我们创建两个 js 文件 support.js 和 master.js。support.js 文件代码console.log(进程 process.argv[2] 执行。 );master.js 文件代码const fs require(fs); const child_process require(child_process); for(var i0; i3; i) { var workerProcess child_process.spawn(node, [support.js, i]); workerProcess.stdout.on(data, function (data) { console.log(stdout: data); }); workerProcess.stderr.on(data, function (data) { console.log(stderr: data); }); workerProcess.on(close, function (code) { console.log(子进程已退出退出码 code); }); }执行以上代码输出结果为$ node master.js stdout: 进程 0 执行。 子进程已退出退出码 0 stdout: 进程 1 执行。 子进程已退出退出码 0 stdout: 进程 2 执行。 子进程已退出退出码 0fork 方法child_process.fork 是 spawn() 方法的特殊形式用于创建进程语法格式如下child_process.fork(modulePath[, args][, options])参数参数说明如下modulePath String将要在子进程中运行的模块args Array 字符串参数数组optionsObjectcwd String 子进程的当前工作目录env Object 环境变量键值对execPath String 创建子进程的可执行文件execArgv Array 子进程的可执行文件的字符串参数数组默认 process.execArgvsilent Boolean 如果为true子进程的stdinstdout和stderr将会被关联至父进程否则它们将会从父进程中继承。默认为falseuid Number 设置用户进程的 IDgid Number 设置进程组的 ID返回的对象除了拥有ChildProcess实例的所有方法还有一个内建的通信信道。实例让我们创建两个 js 文件 support.js 和 master.js。support.js 文件代码console.log(进程 process.argv[2] 执行。 );master.js 文件代码const fs require(fs); const child_process require(child_process); for(var i0; i3; i) { var worker_process child_process.fork(support.js, [i]); worker_process.on(close, function (code) { console.log(子进程已退出退出码 code); }); }执行以上代码输出结果为$ node master.js 进程 0 执行。 子进程已退出退出码 0 进程 1 执行。 子进程已退出退出码 0 进程 2 执行。 子进程已退出退出码 0cluster一端口多进程的 HTTP 服务最小可用的多核 HTTP 服务实例// server-cluster.jsimport cluster from node:cluster;import os from node:os;import http from node:http;import { pbkdf2Sync } from node:crypto;if (cluster.isPrimary) {const cpuCount Math.max(1, os.cpus().length);console.log(主进程 ${process.pid}启动 ${cpuCount} 个工作进程);for (let i 0; i cpuCount; i) cluster.fork();cluster.on(exit, (worker, code) {console.warn(工作进程 ${worker.process.pid} 退出code${code}重启中…);cluster.fork();});} else {const server http.createServer((req, res) {// 模拟 CPU 密集计算pbkdf2Sync(password, salt, 100_000, 64, sha512);res.writeHead(200, {content-type:text/plain; charsetutf-8});res.end(Handled by worker ${process.pid}\n);});server.listen(3000, () {console.log(工作进程 ${process.pid} 监听 3000);});}运行node server-cluster.js多次请求 http://localhost:3000 会看到不同 PID 响应。注意线上多实例一般前置反向代理/负载均衡Nginx、Envoy、K8s Service或由 cluster 在一个端口聚合。WebSocket/粘性会话需要sticky session同一客户端落到同一 worker。可以用四层负载均衡的源地址哈希或在应用层自己做粘滞如按 req.socket.remoteAddress 做分发。优雅重启零停机思路思路主进程收到重载信号 → 先 fork 出新 worker 并等待其 listening → 再 disconnect 旧 worker等请求跑完退出。实例// 摘要示例在 cluster.isPrimary 分支里process.on(SIGUSR2, async () {console.log(收到 SIGUSR2开始优雅重启);const workers Object.values(cluster.workers ?? {});// 1) 启新const fresh cluster.fork();await new Promise(r fresh.once(listening, r));// 2) 逐个下线旧的for (const w of workers) {w?.disconnect();// 超时还没退就强杀setTimeout(() w?.process.kill(SIGKILL), 5000);}});Windows 没有 SIGUSR2可用 HTTP / RPC 管理接口触发重载。自建「进程池」控制并发、复用子进程当你有大量独立 CPU 任务如批量压缩、加密、爬虫解析频繁 fork/spawn 成本高这时需要 Process Pool 复用若干子进程像线程池一样限流。文件pool/worker.js实例// pool/worker.jsprocess.on(message, async (msg) {if (msg.type task) {const { id, payload } msg;// 模拟重任务斐波那契const fib (n) (n 1 ? n : fib(n-1) fib(n-2));const result fib(payload.n);process.send({ type: done, id, result });}});文件pool/index.js实例// pool/index.jsimport { fork } from node:child_process;import os from node:os;export class ProcessPool {constructor({ file, size Math.max(1, os.cpus().length - 1) } {}) {this.file file;this.size size;this.idle [];this.busy new Map(); // worker - taskIdthis.queue [];for (let i 0; i size; i) this._spawn();}_spawn() {const w fork(this.file);w.on(message, (m) {if (m?.type done) {const cb this.callbacks.get(m.id);if (cb) cb.resolve(m.result);this.callbacks.delete(m.id);this._markIdle(w);this._drain();}});w.on(exit, () {// 自动补齐池子this.busy.delete(w);const idx this.idle.indexOf(w);if (idx 0) this.idle.splice(idx, 1);this._spawn();});if (!this.callbacks) this.callbacks new Map();this.idle.push(w);}_markIdle(w) {this.busy.delete(w);if (!this.idle.includes(w)) this.idle.push(w);}_acquire() {return this.idle.length ? this.idle.shift() : null;}_drain() {while (this.queue.length this.idle.length) {const { id, payload, resolve, reject } this.queue.shift();const w this._acquire();this.busy.set(w, id);this.callbacks.set(id, { resolve, reject });w.send({ type: task, id, payload });}}runTask(payload) {const id Math.random().toString(36).slice(2);return new Promise((resolve, reject) {this.queue.push({ id, payload, resolve, reject });this._drain();});}close() {for (const w of this.idle) w.kill();for (const w of this.busy.keys()) w.kill();}}文件pool/demo.js实例// pool/demo.jsimport { ProcessPool } from ./index.js;const pool new ProcessPool({ file: ./pool/worker.js, size: 4 });const tasks Array.from({ length: 10 }, (_, i) pool.runTask({ n: 35 (i % 3) }));const t0 Date.now();const results await Promise.all(tasks);console.log(结果:, results);console.log(耗时(ms):, Date.now() - t0);await new Promise(r setTimeout(r, 100)); // 等待消息刷完pool.close();说明把任务封装成消息进程池负责分配与复用 worker。真实业务可把解析/转码/压缩封装到worker.js里。注意任务超时、重试、幂等与背压队列长度上限。实战小例HTTP 接口把重任务扔进进程池实例// app.jsimport http from node:http;import { ProcessPool } from ./pool/index.js;const pool new ProcessPool({ file: ./pool/worker.js, size: 4 });const server http.createServer(async (req, res) {if (req.url?.startsWith(/fib?)) {const url new URL(req.url, http://localhost);const n Number(url.searchParams.get(n) || 35);try {const result await pool.runTask({ n });res.writeHead(200, {content-type: application/json});res.end(JSON.stringify({ pid: process.pid, n, result }));} catch (e) {res.writeHead(500); res.end(error);}} else {res.writeHead(404).end(Not Found);}});server.listen(3000, () console.log(http://localhost:3000));访问/fib?n38等主进程不被阻塞任务在子进程里算。

相关新闻

HoRain云--Node.js 连接 MySQL

HoRain云--Node.js 连接 MySQL

安装驱动 本教程使用了淘宝定制的 cnpm 命令进行安装: $ cnpm install mysql 连接数据库 在以下实例中根据你的实际配置修改数据库用户名、及密码及数据库名: test.js 文件代码: var mysql require(mysql); var connection mysql.cre…

2026/10/25 20:05:31 阅读更多 →
豆包    LeetCode 39. 组合总和 Java实现

豆包 LeetCode 39. 组合总和 Java实现

题目说明 LeetCode39 组合总和 给定无重复元素候选数组 candidates 和目标 target ,可以重复选取数组元素,找出所有和为 target 的组合。 同一个数字可以多次选用组合顺序无关,不能重复输出组合回溯DFS实现 Java完整代码 java import java.ut…

2026/10/21 7:14:48 阅读更多 →
王者荣耀凯双冰流出装解析:从爆发刺客到持续控制战士的版本转型

王者荣耀凯双冰流出装解析:从爆发刺客到持续控制战士的版本转型

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 17:31:06 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/26 12:00:21 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/25 22:36:10 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/26 4:52:12 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/26 12:00:21 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/26 5:00:37 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/26 3:16:46 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 12:00:19 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/25 13:21:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 8:34:37 阅读更多 →