进程与子进程
Node.js 进程与子进程详解
Node.js 在单个进程中运行,但通过 process 对象、child_process 模块及 worker_threads 模块提供了对进程的精细控制以及多进程/多线程能力。下面从三个方面展开。
一、process 对象
process 对象是全局对象,提供当前 Node.js 进程的信息和控制能力。
1. 环境变量(process.env)
返回包含用户环境变量的对象。可读取、修改或删除环境变量,但修改仅在当前进程中生效,不影响父进程。
console.log(process.env.NODE_ENV); // 打印环境变量
process.env.MY_VAR = "hello"; // 设置临时变量
delete process.env.MY_VAR; // 删除
2. 命令行参数(process.argv)
返回数组,包含启动 Node.js 进程时的命令行参数:
argv[0]:Node.js 可执行文件路径argv[1]:当前执行的 JavaScript 文件路径- 后续元素为用户传入的参数
// node app.js --name=John --age 30
const args = process.argv.slice(2);
console.log(args); // ['--name=John', '--age', '30']
常用第三方库如 minimist 或 commander 可简化解析。
3. 退出(process.exit 与 exit 事件)
process.exit([code]):终止进程,默认code为0(成功)。非零表示错误。process.on('exit', (code) => {...}):在进程退出前执行(只能执行同步操作,异步会被忽略)。
process.on("exit", code => {
console.log(`进程即将退出,退出码:${code}`);
});
process.exit(1); // 触发 exit 事件,然后退出
4. 信号处理(process.on('SIG...'))
可以监听 POSIX 信号,如 SIGINT(Ctrl+C)、SIGTERM、SIGUSR1 等。
process.on("SIGINT", () => {
console.log("收到 SIGINT,优雅退出");
process.exit(0);
});
5. 当前工作目录(process.cwd() 与 process.chdir())
process.cwd():返回当前工作目录(即执行node命令时的目录)。process.chdir(path):改变工作目录。
console.log(process.cwd()); // /home/user/project
process.chdir("/tmp");
console.log(process.cwd()); // /tmp
6. 文件权限掩码(process.umask())
设置或返回 Node.js 进程的文件权限掩码,影响新创建文件的权限。不推荐频繁修改。
const oldMask = process.umask(0o022);
console.log(oldMask.toString(8));
7. 内存使用(process.memoryUsage())
返回对象,描述 Node.js 进程的内存使用情况(单位:字节)。
rss:常驻内存大小(Resident Set Size)heapTotal:V8 堆内存总分配量heapUsed:实际使用的堆内存external:C++ 对象占用的内存
console.log(process.memoryUsage());
此外还有 process.cpuUsage()、process.hrtime() 等高精度性能测量方法。
二、子进程(child_process 模块)
Node.js 可以创建子进程执行外部命令或脚本。四种核心方法:exec、execFile、spawn、fork。它们都基于 spawn,但提供了不同层级的封装。
| 方法 | 特点 | 适用场景 | 回调/输出大小 |
|---|---|---|---|
exec | 执行 shell 命令,将输出缓冲到回调 | 执行简单命令,输出较小 | 有最大缓冲限制(默认 1MB) |
execFile | 直接执行可执行文件,不启动 shell | 执行特定二进制文件,更安全 | 同样有缓冲限制 |
spawn | 流式输出,返回 stdout/stderr 流 | 大量输出或实时处理数据 | 无缓冲上限 |
fork | 专门用于派生新的 Node.js 进程,自带通信 IPC | 在子进程中运行 Node.js 模块,需要进程间通信 | 基于 IPC 消息传递 |
1. exec 与 execFile
const { exec, execFile } = require("child_process");
// exec 使用 shell
exec("ls -la | grep test", (error, stdout, stderr) => {
if (error) console.error(error);
console.log(stdout);
});
// execFile 直接执行程序,更高效
execFile("node", ["--version"], (error, stdout) => {
console.log(stdout); // v18.x.x
});
2. spawn(流式,推荐大输出或实时交互)
const { spawn } = require("child_process");
const ls = spawn("ls", ["-lh", "/usr"]);
ls.stdout.on("data", data => {
console.log(`输出:${data}`);
});
ls.stderr.on("data", data => {
console.error(`错误:${data}`);
});
ls.on("close", code => {
console.log(`子进程退出码:${code}`);
});
3. fork(Node.js 子进程 + IPC)
父进程 parent.js:
const { fork } = require("child_process");
const child = fork("./child.js");
child.on("message", msg => {
console.log("收到子进程消息:", msg);
});
child.send({ hello: "parent" });
子进程 child.js:
process.on("message", msg => {
console.log("父进程消息:", msg);
process.send({ reply: "from child" });
});
fork 的第二个参数可以指定 silent: true 来屏蔽子进程的标准输出。
4. 父子进程通信(IPC)
- 使用
fork时自动建立 IPC 通道,可调用child.send(message)和监听'message'事件。 - 对于
spawn,可以通过stdio: ['ipc']选项启用 IPC,然后使用.send和on('message')通信。
5. 执行外部命令(如 Python 脚本)
const { spawn } = require("child_process");
const python = spawn("python", ["script.py", "arg1"]);
python.stdout.on("data", data => console.log(data.toString()));
python.stderr.on("data", data => console.error(data.toString()));
6. 子进程的其他控制
child.kill([signal]):发送信号终止子进程。child.pid:获取子进程 PID。- 通过
options.detached: true使子进程独立于父进程运行。
三、工作线程(worker_threads)
工作线程用于执行 CPU 密集型 JavaScript 任务,不阻塞主线程。它们共享同一进程的 V8 实例,但运行在独立的事件循环中。
1. 基本用法
主线程 main.js:
const { Worker } = require("worker_threads");
const worker = new Worker("./worker.js", { workerData: { start: 1 } });
worker.on("message", result => console.log("结果:", result));
worker.on("error", err => console.error(err));
worker.on("exit", code => console.log(`Worker 退出码 ${code}`));
// 向 worker 发送消息
worker.postMessage({ cmd: "compute" });
工作线程 worker.js:
const { parentPort, workerData } = require("worker_threads");
// 接收主线程消息
parentPort.on("message", msg => {
if (msg.cmd === "compute") {
let result = heavyComputation(workerData.start);
parentPort.postMessage(result);
}
});
function heavyComputation(start) {
// 模拟 CPU 密集型任务
let sum = 0;
for (let i = 0; i < 1e8; i++) sum += i;
return sum;
}
2. 共享内存(SharedArrayBuffer)
worker_threads 支持使用 SharedArrayBuffer 在主线程和工作线程之间共享内存,配合 Atomics 实现同步。
// 主线程
const { Worker } = require("worker_threads");
const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 2);
const sharedArray = new Int32Array(sharedBuffer);
sharedArray[0] = 0;
const worker = new Worker("./worker.js", { workerData: sharedBuffer });
worker.on("message", () => {
console.log("主线程读取 sharedArray[0] =", sharedArray[0]); // 1
});
// worker.js
const { parentPort, workerData } = require("worker_threads");
const sharedArray = new Int32Array(workerData);
sharedArray[0] = 1;
parentPort.postMessage("done");
3. 与 child_process 的对比
| 特性 | child_process | worker_threads |
|---|---|---|
| 进程/线程 | 独立进程 | 同一进程内的线程 |
| 资源开销 | 较大(独立内存、进程等) | 较小(共享 V8 实例) |
| 适用任务 | I/O 密集、执行外部命令 | CPU 密集计算 |
| 通信方式 | IPC 或 stdio | 消息传递(更高效) |
| 全局状态隔离 | 完全隔离,无共享 | 可共享内存,但默认不共享全局变量 |
| 使用场景 | 运行外部脚本、多进程模型 | 密集计算、并行处理 |
总结
process对象提供了进程级控制:环境变量、参数、退出、信号、工作目录、内存监控等。child_process模块用于创建子进程,四种方法各有侧重:exec/execFile适合简单命令且输出少;spawn适合流式输出;fork专用于 Node.js 模块通信。worker_threads解决 CPU 密集型任务阻塞主线程的问题,支持共享内存,与子进程相比更轻量。
推荐参考资料
- Node.js 官方文档:Process、Child Process、Worker Threads
- 深入理解:
child_process四种方法的区别与源码分析 - 实践:使用 worker_threads 优化计算性能
掌握这些 API,可以构建稳定、高效的多进程/多线程 Node.js 应用。
相关文章
文件系统
Node.js 的 fs 模块提供了与文件系统交互的 API,几乎涵盖了所有标准文件操作。它支持三种风格的 API:同步、回调式异步 和 Promise 式异步。下面详细介绍这些 API 的选择策略、流式读写、文件监视以及常用操作。
缓冲区
Buffer 是 Node.js 全局对象,用于处理二进制数据流(如文件、网络数据)。在 ES6 引入 TypedArray 之前,Buffer 是 Node.js 处理二进制的主要方式;现在 Buffer 实现了 Uint8Arra…
路径处理
path 模块提供了用于处理和转换文件路径的实用工具。它是 Node.js 核心模块,无需安装即可直接使用。由于不同操作系统(Windows、Linux、macOS)的路径分隔符不同(Windows 使用反斜杠 \,POSIX 使用正…
网络编程
Node.js 提供了丰富的内置模块,用于构建网络应用。下面从 HTTP/HTTPS、WebSocket、TCP/UDP 以及 DNS 解析四个方面进行深入介绍。
流
流是 Node.js 中处理有序数据的抽象接口,用于以连续方式异步读取或写入数据。它们特别适合处理大文件、网络通信或任何可能无限产生的数据。Node.js 内置的 stream 模块提供了四种基本流类型,以及用于组合和错误处理的工具函数。
其他常用模块
以下是 Node.js 中几个重要内置模块的详细介绍,涵盖事件触发器、定时器、加密、压缩、操作系统信息和实用工具。