进程创建
一、认识
Node.js
中创建子进程的方法主要包括 spawn
、exec
、execFile
和 fork
。其中:
-
spawn
是最底层的API
, 直接启动一个新的进程, 不会使用shell
, 返回一个ChildProcess
实例, 支持stdin
、stdout
、stderr
的流式数据传输。不会一次性将数据加载到内存中, 而是采用流式传输, 适合处理大量数据。可通过pipe
或监听data
事件逐步消费数据, 避免内存溢出, 要监听error
和exit
事件, 确保在子进程出错时及时捕获并处理异常。当需要与子进程进行长时间、连续的数据交互时, 比如处理大文件、数据流传输等,spawn()
是一个理想选择。 -
exec
在shell
环境中执行命令, 适合直接运行系统命令, 将命令输出一次性缓冲在内存中, 通过回调函数返回结果。默认缓冲区大小有限(通常约200KB
), 当输出数据量过大时可能会导致内存溢出, 必要时可以通过选项{ maxBuffer: 1024 * 1024 * 2 }
调整缓冲区大小。适合执行一次性命令、输出量较小的场景, 比如查询系统状态、运行简单脚本等。 -
execFile()
与exec()
类似, 但不会通过shell
执行, 而是直接执行指定的文件, 更加安全,避免了shell
注入风险,同时性能略高于exec
。使用时同样可以设置缓冲区大小,并处理回调中的错误。当你要执行一个可执行文件, 并且对安全性和性能有要求时,execFile()
是一个不错的选择。 -
fork()
, 是spawn()
的一种特殊封装,用于专门创建新的Node.js
进程。内置了进程间通信(IPC
)通道, 支持通过process.send()
和process.on('message')
进行双向通信。fork()
非常适合传递JSON
对象, 实现任务分配与结果整合, 需要设计好消息协议, 确保数据格式和通信顺畅。fork()
常用于构建多进程架构, 也可结合Node.js
内置的cluster
模块利用多核CPU
, 提高应用并发处理能力。同样需要监听error
、exit
事件, 确保主进程能够感知子进程状态, 及时重启或清理资源。当需要隔离计算密集型任务, 或者主进程需要高效与子进程通信时,fork
是理想选择。
在实际项目中, 我曾利用 fork
创建多个工作进程, 通过 IPC
机制分发任务,显著提高了服务的并发处理能力; 而在处理大文件数据传输时, 我使用 spawn
将数据流直接传递给文件写入流,有效避免内存瓶颈。
二、spawn
spawn
是最底层的 API
, 直接启动一个新的进程, 不会使用 shell
, 返回一个 ChildProcess
实例, 支持 stdin
、stdout
、stderr
的流式数据传输。不会一次性将数据加载到内存中, 而是采用流式传输, 适合处理大量数据。可通过 pipe
或监听 data
事件逐步消费数据, 避免内存溢出, 要监听 error
和 exit
事件, 确保在子进程出错时及时捕获并处理异常。当需要与子进程进行长时间、连续的数据交互时, 比如处理大文件、数据流传输等, 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
exec
在 shell
环境中执行命令, 适合直接运行系统命令, 将命令输出一次性缓冲在内存中, 通过回调函数返回结果。默认缓冲区大小有限(通常约 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
, 提高应用并发处理能力。同样需要监听 error
、exit
事件, 确保主进程能够感知子进程状态, 及时重启或清理资源。当需要隔离计算密集型任务, 或者主进程需要高效与子进程通信时, 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
选项用于配置子进程的标准输入、标准输出、标准错误以及其他可能的文件描述符通道。它允许你精细地控制父进程与子进程之间的数据流如何传递。你可以设置为 pipe
、ignore
或 inherit
,或者直接传递文件描述符。
-
pipe
: 表示创建一个管道,父进程可以通过该管道与子进程进行通信。 -
inherit
: 则表示子进程继承父进程相应的流,这样输出和错误信息会直接显示在父进程的终端上。 -
ignore
: 则忽略对应的流,不进行任何传输。
默认情况下, spawn
会为子进程创建三个管道, 对应 stdin
、stdout
、stderr
, 也就是默认相当于设置 stdio: ['pipe', 'pipe', 'pipe']
。
例如, 下面的代码展示了默认的 stdio
配置:
const { spawn } = require('child_process');
const child = spawn('ls', ['-lh'], {
// 默认 stdio: ['pipe', 'pipe', 'pipe']
});