跳到主要内容

child_process 模块

异步 API

child_process.exec()
  • child_process.exec(command[,options][,callback]);
  • 回调函数方式;
  • 默认创建一个终端,直接执行 bash/shell 命令;
    • 存在注入风险;
  • 使用回调函数,非流式输出,暂存到 buffer 中,存在数据流风险;
    • 通过 maxBuffer 设置最大存储量;
import { exec } from "child_process";

const controller = new AbortController();
const { signal } = controller;
exec(
"cat *.js missing_file | wc -l",
{
cwd: process.cwd(), // 工作目录
env: process.env, // 环境变量
encoding: "utf8", // 编码
shell: "/bin/sh", // shell
signal: signal, // AbortController 标识
timeout: 0, // 超时时间, 超时报错
maxBuffer: 1024 * 1024, // 最大输出字节数, 超过报错
killSignal: "SIGTERM",
uid: 1,
gid: 1,
windowsHide: false, // 是否隐藏终端
},
(error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
}
);

controller.abort();
child_process.execFile()
  • child_process.execFile(file[,args][,options][,callback]);
  • child_process.exec() 的不使用终端版本;
  • 直接执行可执行文件,不会使用 shell/bash;
import { execFile } from "child_process";

const controller = new AbortController();
const { signal } = controller;
const child = execFile(
"node",
["--version"],
{
cwd: process.cwd(), // 工作目录
env: process.env, // 环境变量
encoding: "utf8", // 编码
shell: "/bin/sh", // shell
signal, // AbortController 标识
timeout: 0, // 超时时间, 超时报错
maxBuffer: 1024 * 1024, // 最大输出字节数, 超过报错
killSignal: "SIGTERM",
uid: 1,
gid: 1,
windowsHide: false, // 是否隐藏终端
},
(error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
}
);

controller.abort();
child_process.spawn()
  • child_process.spawn(command[,args][,options]);
  • 事件响应模式;
  • 默认不会创建终端;
  • 流式输出,可自定义输入/输出/报错流;
import { spawn } from "child_process";

const controller = new AbortController();
const { signal } = controller;
const child = spawn("./child.js", ["--version"], {
cwd: process.cwd(), // 工作目录
env: process.env, // 环境变量
argv0: "./child.js", // 传递给 child_process 的第一个参数
detached: false, // 是否与父进程分离
stdio: ["pipe", "pipe", "pipe"], // 依次设置 stdin, stdout, stderr
uid: 1,
gid: 1,
shell: false, // 是否使用终端
windowsHide: false, // 是否隐藏终端
signal, // AbortController 标识
killSignal: "SIGTERM",
timeout: 0, // 超时时间, 超时报错
});

controller.abort();
child_process.fork()
  • child_process.fork(modulePath[,args][,options]);
  • 基于 spawn 实现,自动建立 IPC 机制;
    • 可直接使用 message 事件 和 send 方法;
import { fork } from "child_process";

const controller = new AbortController();
const { signal } = controller;
const child = fork("./child.js", ["--version"], {
cwd: process.cwd(), // 工作目录
detached: false, // 是否与父进程分离
env: process.env, // 环境变量
execPath: "./child.js", // 创建子进程的可执行文件
execArgv: process.execArgv, // 传递给可执行文件的参数列表
gid: 1,
signal, // AbortController 标识
killSignal: "SIGTERM",
silent: false, // 是否使用父进程的 stdin, stdout, stderr
stdio: ["pipe", "pipe", "pipe"], // 依次设置 stdin, stdout, stderr
uid: 1,
timeout: 0, // 超时时间, 超时报错
});

controller.abort();

const n = child_process.fork("./child.js");
n.on("message", function (m) {
console.log("PARENT got message:", m);
});
n.send({ hello: "world" });
options.stdio
  • 常量;
    • 'pipe' (默认值):['pipe','pipe','pipe'];
    • 'overlapped':['overlapped','overlapped','overlapped'];
    • 'ignore':['ignore','ignore','ignore'];
    • 'inherit':['inherit','inherit','inherit'];
  • pipe:输出到父进程 stdio;
  • overlapped:同 pipe 但是输出到 file handler,非 windows 等效于 pipe;
  • ipc:创建父进程和子进程的 IPC 通道;
  • ignore:忽略子进程的 fd;
  • inherit:等效于 process.stdin,process.stdout 和 process.stderr;
  • stream:某个 stream 实例;
  • null/undefined:使用默认值;

ChildProcess

事件
  • 需要建立 IPC 通道后,message 事件才有效;
  • 否则需要监听 stdio 中的 message 事件;
const { spawn } = require("node:child_process");
const ls = spawn("ls", ["-lh", "/usr"]);

// 关闭所有的 stdio 后触发, 多个子进程可能使用同一个 stdio, 在 exit 事件后出发
ls.on("close", (code) => {
console.log(`child process close all stdio with code ${code}`);
});

// 调用 subprocess.disconnect() 后触发
ls.on("disconnect", () => {
console.log(`child process disconnect`);
});

// 无法执行, 报错, controller.abort() 后触发
ls.on("error", (err) => {
console.log(err);
});

// 当前子进程结束后触发
ls.on("exit", (code) => {
console.log(`child process exit with code ${code}`);
});

// 子进程调用 process.send() 触发
ls.on("message", (message) => {
console.log(message);
});

// 子进程成功生成后触发
ls.on("spawn", () => {
console.log("success");
});
属性
subprocess.channel; // IPC 通道
subprocess.connected; // subprocess.disconnect() 调用后位 false
subprocess.exitCode; // 子进程退出的 exit code
subprocess.killed; // subprocess.kill() 调用后为 true
subprocess.pid; // pid
subprocess.stderr; // Readable Stream, 子进程报错信息
subprocess.stdout; // Readable Stream, 子进程输出信息
subprocess.stdin; // Writable Stream, 子进程的输入信息
subprocess.stdio; // [stdin, stdout, stderr]
方法
  • subprocess.send(message[,sendHandle[,options]][,callback]);
    • 通过 IPC 通道发送信息,触发 message 事件;
  • subprocess.disconnect():关闭父进程和子进程的 IPC 通道
  • subprocess.kill([signal]):杀死子进程
  • subprocess.ref():恢复子进程的移除引用计数
  • subprocess.unref():移除子进程的移除引用计数
// parent.js
const cp = require("node:child_process");
const n = cp.fork(`${__dirname}/sub.js`);
n.on("message", (m) => {
console.log("PARENT got message:", m);
});
// 触发子进程 message 事件, 打印 CHILD got message: { hello: 'world' }
n.send({ hello: "world" });
// sub.js
process.on("message", (m) => {
console.log("CHILD got message:", m);
});
// 触发父进程 message 事件, 打印 PARENT got message: { foo: 'bar', baz: null }
process.send({ foo: "bar", baz: NaN });

subprocess.disconnect();
subprocess.kill([signal]);
subprocess.ref();
subprocess.unref();

最佳实践

node IPC 方法
  • 消息队列:多个进程消息在一个队列中;
  • 消息传递:进程之间通过发送和接受消息进行消息同步;
  • 管道:一个进程的输出变为另一个进程的输入;
  • 信号:进程之间发送通知和命令;
  • 共享内存:多个进程共享一个内存空间,往往需要引入信号量解决同步问题;
  • socket;
父子进程分离
  • 一般情况父进程会等待子进程;
  • 使用 unref() 使父进程不等待子进程强制退出;
  • 要求子进程和父进程之间不能建立 IPC 通道,即 stdio 设置为 ignore 或者与父进程不相关的 stdio;
const { spawn } = require("node:child_process");
const subprocess = spawn(process.argv[0], ["child_program.js"], {
detached: true,
stdio: "ignore",
});
subprocess.unref();