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/9/2 16:48:06 阅读更多 →
豆包    LeetCode 39. 组合总和 Java实现

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

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

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

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

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

2026/9/2 16:48:06 阅读更多 →

最新新闻

Hermes Studio智能体实战:从目标描述到生产落地

Hermes Studio智能体实战:从目标描述到生产落地

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

2026/9/2 17:34:33 阅读更多 →
FPGA部署Tiny LLM:INT4量化与硬件定制如何实现21000 tok/s边缘推理

FPGA部署Tiny LLM:INT4量化与硬件定制如何实现21000 tok/s边缘推理

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

2026/9/2 17:34:33 阅读更多 →
《重返未来:1999》挑战4-3通关策略:机制反制与行动轴规划

《重返未来:1999》挑战4-3通关策略:机制反制与行动轴规划

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

2026/9/2 17:34:33 阅读更多 →
技术人如何平衡工作与生活:从效率工具到数字边界实践

技术人如何平衡工作与生活:从效率工具到数字边界实践

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

2026/9/2 17:34:33 阅读更多 →
不只会打字回复:Open-LLM-VTuber接入大模型,让AI能听、能说、还能看到屏幕

不只会打字回复:Open-LLM-VTuber接入大模型,让AI能听、能说、还能看到屏幕

🔥承渊政道:个人主页 ❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C知识内容》 《Linux系统知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》 《MySQL数据库学习》 《Python知识内容》 《cpolar知识学习》 ✨逆境不…

2026/9/2 17:34:33 阅读更多 →
降AI率工具推荐:工商管理硕士MBA论文AIGC超标4.8元亲测达标完整方案

降AI率工具推荐:工商管理硕士MBA论文AIGC超标4.8元亲测达标完整方案

降AI率工具推荐:工商管理硕士MBA论文AIGC超标4.8元亲测达标完整方案 降AI率超标,工具选错比不选更耽误事。 综合试用和口碑,工商管理硕士MBA论文降AI率首推嘎嘎降AI(www.aigcleaner.com),4.8元一篇&#…

2026/9/2 17:33:33 阅读更多 →

日新闻

QEMU为什么能模拟不同CPU?从ISA、CPU模型到指令翻译讲起

QEMU为什么能模拟不同CPU?从ISA、CPU模型到指令翻译讲起

1. 引言:一个软件为何能“伪装”成不同CPUQEMU 是一款广为人知的开源模拟器,它既能在一台 x86 电脑上运行 ARM 系统,也能在 ARM 开发板上启动 x86 的 Linux 发行版。很多人第一次接触 QEMU 时都会好奇:一个纯软件程序,…

2026/9/2 0:00:30 阅读更多 →
单片机计算机毕设之基于 STM32 或 51 单片机的感知式智能垃圾桶硬件控制系统设计 基于 STM32 或 51 单片机的安全防护型智能垃圾桶装置设计(025005)

单片机计算机毕设之基于 STM32 或 51 单片机的感知式智能垃圾桶硬件控制系统设计 基于 STM32 或 51 单片机的安全防护型智能垃圾桶装置设计(025005)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/9/2 0:00:30 阅读更多 →
单片机计算机毕设之基于 ESP8266 的智能垃圾分类桶 APP 监控系统设计与实现 基于单片机的超声波满溢检测垃圾分类装置设计(025105)

单片机计算机毕设之基于 ESP8266 的智能垃圾分类桶 APP 监控系统设计与实现 基于单片机的超声波满溢检测垃圾分类装置设计(025105)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/9/2 0:00:30 阅读更多 →

周新闻

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析

每年校招季我都会接触不少准备数据库方向笔试的同学,看到最多的状态就是:简历上写着“熟悉 MySQL”“了解索引优化”,一碰到数据库管理工程师的笔试卷,却在索引、事务、锁、备份恢复这些题目上翻车。网易这套 2018 校园招聘数据库…

2026/9/1 19:44:48 阅读更多 →
数字电路时序基石:深入理解建立时间与保持时间

数字电路时序基石:深入理解建立时间与保持时间

1. 这不是“背公式”的事:时间参数到底在约束什么你翻过数字电路教材,一定见过这两个词:建立时间(Setup Time)和保持时间(Hold Time)。它们常被并列写在触发器(Flip-Flop&#xff09…

2026/9/1 18:13:19 阅读更多 →
蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

1. 项目缘起:从赛题到超声波测距机的诞生第八届蓝桥杯单片机设计与开发国赛的题目,我至今记忆犹新。它没有直接给出一个花哨的名字,而是用“超声波测距机”这个朴实无华的功能描述,精准地勾勒出了考核的核心。对于当时备赛的我而言…

2026/9/2 1:01:37 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/2 1:06:24 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/2 1:01:13 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/2 2:01:56 阅读更多 →