FORMA

进程与子进程

Node.js 进程与子进程详解

Node.js 在单个进程中运行,但通过 process 对象、child_process 模块及 worker_threads 模块提供了对进程的精细控制以及多进程/多线程能力。下面从三个方面展开。

一、process 对象

process 对象是全局对象,提供当前 Node.js 进程的信息和控制能力。

1. 环境变量(process.env

返回包含用户环境变量的对象。可读取、修改或删除环境变量,但修改仅在当前进程中生效,不影响父进程。

js
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 文件路径
  • 后续元素为用户传入的参数
js
// node app.js --name=John --age 30
const args = process.argv.slice(2);
console.log(args); // ['--name=John', '--age', '30']

常用第三方库如 minimistcommander 可简化解析。

3. 退出(process.exitexit 事件)

  • process.exit([code]):终止进程,默认 code0(成功)。非零表示错误。
  • process.on('exit', (code) => {...}):在进程退出前执行(只能执行同步操作,异步会被忽略)。
js
process.on("exit", code => {
    console.log(`进程即将退出,退出码:${code}`);
});
process.exit(1); // 触发 exit 事件,然后退出

4. 信号处理(process.on('SIG...')

可以监听 POSIX 信号,如 SIGINT(Ctrl+C)、SIGTERMSIGUSR1 等。

js
process.on("SIGINT", () => {
    console.log("收到 SIGINT,优雅退出");
    process.exit(0);
});

5. 当前工作目录(process.cwd()process.chdir()

  • process.cwd():返回当前工作目录(即执行 node 命令时的目录)。
  • process.chdir(path):改变工作目录。
js
console.log(process.cwd()); // /home/user/project
process.chdir("/tmp");
console.log(process.cwd()); // /tmp

6. 文件权限掩码(process.umask()

设置或返回 Node.js 进程的文件权限掩码,影响新创建文件的权限。不推荐频繁修改。

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++ 对象占用的内存
js
console.log(process.memoryUsage());

此外还有 process.cpuUsage()process.hrtime() 等高精度性能测量方法。

二、子进程(child_process 模块)

Node.js 可以创建子进程执行外部命令或脚本。四种核心方法:execexecFilespawnfork。它们都基于 spawn,但提供了不同层级的封装。

方法特点适用场景回调/输出大小
exec执行 shell 命令,将输出缓冲到回调执行简单命令,输出较小有最大缓冲限制(默认 1MB)
execFile直接执行可执行文件,不启动 shell执行特定二进制文件,更安全同样有缓冲限制
spawn流式输出,返回 stdout/stderr大量输出或实时处理数据无缓冲上限
fork专门用于派生新的 Node.js 进程,自带通信 IPC在子进程中运行 Node.js 模块,需要进程间通信基于 IPC 消息传递

1. execexecFile

js
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(流式,推荐大输出或实时交互)

js
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

js
const { fork } = require("child_process");
const child = fork("./child.js");

child.on("message", msg => {
    console.log("收到子进程消息:", msg);
});
child.send({ hello: "parent" });

子进程 child.js

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,然后使用 .sendon('message') 通信。

5. 执行外部命令(如 Python 脚本)

js
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

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

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 实现同步。

js
// 主线程
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_processworker_threads
进程/线程独立进程同一进程内的线程
资源开销较大(独立内存、进程等)较小(共享 V8 实例)
适用任务I/O 密集、执行外部命令CPU 密集计算
通信方式IPC 或 stdio消息传递(更高效)
全局状态隔离完全隔离,无共享可共享内存,但默认不共享全局变量
使用场景运行外部脚本、多进程模型密集计算、并行处理

总结

  • process 对象提供了进程级控制:环境变量、参数、退出、信号、工作目录、内存监控等。
  • child_process 模块用于创建子进程,四种方法各有侧重:exec/execFile 适合简单命令且输出少;spawn 适合流式输出;fork 专用于 Node.js 模块通信。
  • worker_threads 解决 CPU 密集型任务阻塞主线程的问题,支持共享内存,与子进程相比更轻量。

推荐参考资料

  • Node.js 官方文档:ProcessChild ProcessWorker Threads
  • 深入理解:child_process 四种方法的区别与源码分析
  • 实践:使用 worker_threads 优化计算性能

掌握这些 API,可以构建稳定、高效的多进程/多线程 Node.js 应用。

Series

core

6 / 7