NodeJS模块研究 - child_process

掌握 nodejs 的 child_process 模块能够极大提高 nodejs 的开发能力,例如主从进程来优化 CPU 计算的问题,多进程开发等等。本文从以下几个方面介绍 child_process 模块的使用:

  • 创建子进程
  • 父子进程通信
  • 独立子进程
  • 进程管道

🔍 关注公众号“心谭博客” / 👉 查看原文: xxoo521.com

# 创建子进程

nodejs 的 child_process 模块创建子进程的方法:spawn, fork, exec, execFile。它们的关系如下:

  • fork, exec, execFile 都是通过 spawn 来实现的。
  • exec 默认会创建 shell。execFile 默认不会创建 shell,意味着不能使用 I/O 重定向、file glob,但效率更高。
  • spawn、exec、execFile 都有同步版本,可能会造成进程阻塞。

child_process.spawn()的使用:

const { spawn } = require("child_process");
// 返回ChildProcess对象,默认情况下其上的stdio不为null
const ls = spawn("ls", ["-lh"]);

ls.stdout.on("data", (data) => {
  console.log(`stdout: ${data}`);
});

ls.stderr.on("data", (data) => {
  console.error(`stderr: ${data}`);
});

ls.on("close", (code) => {
  console.log(`子进程退出,退出码 ${code}`);
});

child_process.exec()的使用:

const { exec } = require("child_process");
// 通过回调函数来操作stdio
exec("ls -lh", (err, stdout, stderr) => {
  if (err) {
    console.error(`执行的错误: ${err}`);
    return;
  }
  console.log(`stdout: ${stdout}`);
  console.error(`stderr: ${stderr}`);
});

# 父子进程通信

fork()返回的 ChildProcess 对象,监听其上的 message 事件,来接受子进程消息;调用 send 方法,来实现 IPC。

parent.js 代码如下:

const { fork } = require("child_process");
const cp = fork("./sub.js");
cp.on("message", (msg) => {
  console.log("父进程收到消息:", msg);
});
cp.send("我是父进程");

sub.js 代码如下:

process.on("message", (m) => {
  console.log("子进程收到消息:", m);
});

process.send("我是子进程");

运行后结果:

父进程收到消息: 我是子进程
子进程收到消息: 我是父进程

# 独立子进程

在正常情况下,父进程一定会等待子进程退出后,才退出。如果想让父进程先退出,不受到子进程的影响,那么应该:

  • 调用 ChildProcess 对象上的unref()
  • options.detached 设置为 true
  • 子进程的 stdio 不能是连接到父进程

main.js 代码如下:

const { spawn } = require("child_process");
const subprocess = spawn(process.argv0, ["sub.js"], {
  detached: true,
  stdio: "ignore",
});

subprocess.unref();

sub.js 代码如下:

setInterval(() => {}, 1000);

# 进程管道

options.stdio 选项用于配置在父进程和子进程之间建立的管道。 默认情况下,子进程的 stdin、 stdout 和 stderr 会被重定向到 ChildProcess 对象上相应的 subprocess.stdin、subprocess.stdout 和 subprocess.stderr 流。 这意味着可以通过监听其上的 data事件,在父进程中获取子进程的 I/O 。

可以用来实现“重定向”:

const fs = require("fs");
const child_process = require("child_process");

const subprocess = child_process.spawn("ls", {
  stdio: [
    0, // 使用父进程的 stdin 用于子进程。
    "pipe", // 把子进程的 stdout 通过管道传到父进程 。
    fs.openSync("err.out", "w"), // 把子进程的 stderr 定向到一个文件。
  ],
});

也可以用来实现"管道运算符":

const { spawn } = require("child_process");

const ps = spawn("ps", ["ax"]);
const grep = spawn("grep", ["ssh"]);

ps.stdout.on("data", (data) => {
  grep.stdin.write(data);
});

ps.stderr.on("data", (err) => {
  console.error(`ps stderr: ${err}`);
});

ps.on("close", (code) => {
  if (code !== 0) {
    console.log(`ps 进程退出,退出码 ${code}`);
  }
  grep.stdin.end();
});

grep.stdout.on("data", (data) => {
  console.log(data.toString());
});

grep.stderr.on("data", (data) => {
  console.error(`grep stderr: ${data}`);
});

grep.on("close", (code) => {
  if (code !== 0) {
    console.log(`grep 进程退出,退出码 ${code}`);
  }
});

# 参考链接