跳到主要内容

进程创建

2024年04月09日
柏拉文
越努力,越幸运

一、认识


Node.js 中创建子进程的方法主要包括 spawnexecexecFilefork。其中:

  1. spawn 是最底层的 API, 直接启动一个新的进程, 不会使用 shell, 返回一个 ChildProcess 实例, 支持 stdinstdoutstderr 的流式数据传输。不会一次性将数据加载到内存中, 而是采用流式传输, 适合处理大量数据。可通过 pipe 或监听 data 事件逐步消费数据, 避免内存溢出, 要监听 errorexit 事件, 确保在子进程出错时及时捕获并处理异常。当需要与子进程进行长时间、连续的数据交互时, 比如处理大文件、数据流传输等, spawn() 是一个理想选择。

  2. execshell 环境中执行命令, 适合直接运行系统命令, 将命令输出一次性缓冲在内存中, 通过回调函数返回结果。默认缓冲区大小有限(通常约 200KB), 当输出数据量过大时可能会导致内存溢出, 必要时可以通过选项 { maxBuffer: 1024 * 1024 * 2 } 调整缓冲区大小。适合执行一次性命令、输出量较小的场景, 比如查询系统状态、运行简单脚本等。

  3. execFile()exec() 类似, 但不会通过 shell 执行, 而是直接执行指定的文件, 更加安全,避免了 shell 注入风险,同时性能略高于 exec。使用时同样可以设置缓冲区大小,并处理回调中的错误。当你要执行一个可执行文件, 并且对安全性和性能有要求时,execFile() 是一个不错的选择。

  4. fork() , 是 spawn() 的一种特殊封装,用于专门创建新的 Node.js 进程。内置了进程间通信(IPC)通道, 支持通过 process.send()process.on('message') 进行双向通信。fork() 非常适合传递 JSON 对象, 实现任务分配与结果整合, 需要设计好消息协议, 确保数据格式和通信顺畅。fork() 常用于构建多进程架构, 也可结合 Node.js 内置的 cluster 模块利用多核 CPU, 提高应用并发处理能力。同样需要监听 errorexit 事件, 确保主进程能够感知子进程状态, 及时重启或清理资源。当需要隔离计算密集型任务, 或者主进程需要高效与子进程通信时, fork 是理想选择。

在实际项目中, 我曾利用 fork 创建多个工作进程, 通过 IPC 机制分发任务,显著提高了服务的并发处理能力; 而在处理大文件数据传输时, 我使用 spawn 将数据流直接传递给文件写入流,有效避免内存瓶颈。

二、spawn


spawn 是最底层的 API, 直接启动一个新的进程, 不会使用 shell, 返回一个 ChildProcess 实例, 支持 stdinstdoutstderr 的流式数据传输。不会一次性将数据加载到内存中, 而是采用流式传输, 适合处理大量数据。可通过 pipe 或监听 data 事件逐步消费数据, 避免内存溢出, 要监听 errorexit 事件, 确保在子进程出错时及时捕获并处理异常。当需要与子进程进行长时间、连续的数据交互时, 比如处理大文件、数据流传输等, spawn() 是一个理想选择。

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

const childSpawn = spawn("ls", ["-h", "/usr"]);

childSpawn.stdout.on("data", (data) => {
console.log(`spawn 输出: ${data}`);
});

childSpawn.stderr.on("data", (data) => {
console.log(`spawn 错误: ${data}`);
});

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

三、exec


execshell 环境中执行命令, 适合直接运行系统命令, 将命令输出一次性缓冲在内存中, 通过回调函数返回结果。默认缓冲区大小有限(通常约 200KB), 当输出数据量过大时可能会导致内存溢出, 必要时可以通过选项 { maxBuffer: 1024 * 1024 * 2 } 调整缓冲区大小。适合执行一次性命令、输出量较小的场景, 比如查询系统状态、运行简单脚本等。

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

exec("ls -h /usr", { maxBuffer: 1024 * 1024 * 2 }, (error, stdout, stderr) => {
if (error) {
console.log(`exec 函数执行错误: ${error}`);
return;
}

console.log(`exec 输出: ${stdout}`);
});

四、execFile


execFile()exec() 类似, 但不会通过 shell 执行, 而是直接执行指定的文件, 更加安全,避免了 shell 注入风险,同时性能略高于 exec。使用时同样可以设置缓冲区大小,并处理回调中的错误。当你要执行一个可执行文件, 并且对安全性和性能有要求时,execFile() 是一个不错的选择。

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

execFile(
"ls",
["-lh", "/usr"],
{ maxBuffer: 1024 * 1024 * 2 },
(error, stdout, stderr) => {
if (error) {
console.log(`execFile 执行错误: ${error}`);
return;
}

console.log(`execFile 输出: ${stdout}`);
}
);

五、fork


fork() , 是 spawn() 的一种特殊封装,用于专门创建新的 Node.js 进程。内置了进程间通信(IPC)通道, 支持通过 process.send() 和 process.on('message') 进行双向通信。fork() 非常适合传递 JSON 对象, 实现任务分配与结果整合, 需要设计好消息协议, 确保数据格式和通信顺畅。fork() 常用于构建多进程架构, 也可结合 Node.js 内置的 cluster 模块利用多核 CPU, 提高应用并发处理能力。同样需要监听 errorexit 事件, 确保主进程能够感知子进程状态, 及时重启或清理资源。当需要隔离计算密集型任务, 或者主进程需要高效与子进程通信时, fork 是理想选择。

5.1 子线程

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

const result = message.data.map((x) => x * 2); // 举例:将数组内数值乘2
process.send({ result });
});

5.2 主线程

const path = require("path");
const { fork } = require("child_process");

const childFork = fork(path.resolve(process.cwd(), "fork", "child.js"));

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

childFork.send({ task: "处理数据", data: [1, 2, 3, 4] });

childFork.on("error", (error) => {
console.log("fork 进程错误:", error);
});

childFork.on("exit", (code) => {
console.log(`fork 进程退出, 退出码: ${code}`);
});

六、问题


6.1 请问创建子进程的方法有哪些, 简单说一下它们的区别?

6.2 请问你知道 spawn 在创建子进程的时候,第三个参数有一个 stdio 选项吗,这个选项的作用是什么,默认的值是什么?

spawn 方法的第三个参数中的 stdio 选项用于配置子进程的标准输入、标准输出、标准错误以及其他可能的文件描述符通道。它允许你精细地控制父进程与子进程之间的数据流如何传递。你可以设置为 pipeignoreinherit,或者直接传递文件描述符。

  • pipe: 表示创建一个管道,父进程可以通过该管道与子进程进行通信。

  • inherit: 则表示子进程继承父进程相应的流,这样输出和错误信息会直接显示在父进程的终端上。

  • ignore: 则忽略对应的流,不进行任何传输。

默认情况下, spawn 会为子进程创建三个管道, 对应 stdinstdoutstderr, 也就是默认相当于设置 stdio: ['pipe', 'pipe', 'pipe']

例如, 下面的代码展示了默认的 stdio 配置:

const { spawn } = require('child_process');
const child = spawn('ls', ['-lh'], {
// 默认 stdio: ['pipe', 'pipe', 'pipe']
});