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

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

# 创建子进程

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}`);
});
1
2
3
4
5
6
7
8
9
10
11
12

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}`);
});
1
2
3
4
5
6
7
8
9
10

# 父子进程通信

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("我是父进程");
1
2
3
4
5
6

sub.js 代码如下:

process.on("message", m => {
    console.log("子进程收到消息:", m);
});
process.send("我是子进程");
1
2
3
4

运行后结果:

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

# 独立子进程

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

  • 调用 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();
1
2
3
4
5
6

sub.js 代码如下:

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

# 进程管道

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 定向到一个文件。
    ]
});
1
2
3
4
5
6
7
8
9

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

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}`);
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 参考链接

来自: NodeJS子进程模块 - process | 心谭博客
作者:心谭
Star仓库:github