Node.js child_process 模块
Node.js 是一个单线程的 JavaScript 运行时环境,但有时候我们需要执行一些系统命令或运行其他程序。这时就需要用到 child_process
模块。
child_process
模块允许 Node.js 应用程序创建子进程,这些子进程可以执行系统命令、运行其他脚本或程序,并与父进程进行通信。
为什么需要 child_process?
- 执行系统命令:比如执行
ls
、git
等命令行工具 - 运行 CPU 密集型任务:避免阻塞 Node.js 主线程
- 与其他语言编写的程序交互:比如调用 Python 或 C++ 程序
- 提高应用性能:通过多进程充分利用多核 CPU
创建子进程的四种方法
1. exec() - 执行简单命令
exec()
方法用于执行简单的 shell 命令,并缓冲输出结果。
实例
const { exec } = require('child_process');
exec('ls -l', (error, stdout, stderr) => {
if (error) {
console.error(`执行错误: ${error}`);
return;
}
console.log(`标准输出:\n${stdout}`);
if (stderr) {
console.error(`标准错误输出:\n${stderr}`);
}
});
exec('ls -l', (error, stdout, stderr) => {
if (error) {
console.error(`执行错误: ${error}`);
return;
}
console.log(`标准输出:\n${stdout}`);
if (stderr) {
console.error(`标准错误输出:\n${stderr}`);
}
});
特点:
- 适合执行简单的命令
- 返回完整的缓冲输出
- 有最大缓冲区限制(默认 200KB)
2. execFile() - 执行可执行文件
execFile()
类似于 exec()
,但直接执行文件而不是通过 shell。
实例
const { execFile } = require('child_process');
execFile('node', ['--version'], (error, stdout, stderr) => {
if (error) {
throw error;
}
console.log(stdout);
});
execFile('node', ['--version'], (error, stdout, stderr) => {
if (error) {
throw error;
}
console.log(stdout);
});
特点:
- 比
exec()
更安全(不通过 shell) - 执行效率更高
- 不能使用 shell 特性(如管道、通配符等)
3. spawn() - 流式处理子进程
spawn()
方法使用流式接口处理子进程的输入输出。
实例
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`标准输出: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`标准错误输出: ${data}`);
});
ls.on('close', (code) => {
console.log(`子进程退出码: ${code}`);
});
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`标准输出: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`标准错误输出: ${data}`);
});
ls.on('close', (code) => {
console.log(`子进程退出码: ${code}`);
});
特点:
- 适合处理大量输出
- 流式处理,内存效率高
- 可以实时处理输出
4. fork() - 创建 Node.js 子进程
fork()
是 spawn()
的特例,专门用于创建新的 Node.js 进程。
实例
const { fork } = require('child_process');
const child = fork('child.js');
child.on('message', (msg) => {
console.log('来自子进程的消息:', msg);
});
child.send({ hello: 'world' });
const child = fork('child.js');
child.on('message', (msg) => {
console.log('来自子进程的消息:', msg);
});
child.send({ hello: 'world' });
特点:
- 创建新的 Node.js 实例
- 内置 IPC(进程间通信)通道
- 适合 CPU 密集型任务
进程间通信 (IPC)
父进程和子进程可以通过 send()
和 message
事件进行通信。
父进程 (parent.js):
实例
const { fork } = require('child_process');
const child = fork('child.js');
child.on('message', (msg) => {
console.log('父进程收到:', msg);
});
child.send({ parent: 'Hello from parent' });
const child = fork('child.js');
child.on('message', (msg) => {
console.log('父进程收到:', msg);
});
child.send({ parent: 'Hello from parent' });
子进程 (child.js):
实例
process.on('message', (msg) => {
console.log('子进程收到:', msg);
process.send({ child: 'Hello from child' });
});
console.log('子进程收到:', msg);
process.send({ child: 'Hello from child' });
});
错误处理
正确处理子进程错误非常重要:
实例
const { spawn } = require('child_process');
const child = spawn('some_command');
child.on('error', (err) => {
console.error('启动子进程失败:', err);
});
child.stderr.on('data', (data) => {
console.error(`子进程错误: ${data}`);
});
child.on('exit', (code, signal) => {
if (code) console.log(`子进程退出码: ${code}`);
if (signal) console.log(`子进程被信号终止: ${signal}`);
});
const child = spawn('some_command');
child.on('error', (err) => {
console.error('启动子进程失败:', err);
});
child.stderr.on('data', (data) => {
console.error(`子进程错误: ${data}`);
});
child.on('exit', (code, signal) => {
if (code) console.log(`子进程退出码: ${code}`);
if (signal) console.log(`子进程被信号终止: ${signal}`);
});
最佳实践
- 安全性:避免直接将用户输入传递给子进程
- 资源管理:及时清理已完成的子进程
- 错误处理:始终处理错误事件和退出事件
- 性能考虑:对于频繁创建的子进程,考虑使用进程池
- 跨平台兼容性:注意不同操作系统的命令差异
总结
child_process
模块是 Node.js 中处理子进程的核心模块,提供了四种创建子进程的方法:
exec()
- 适合简单命令execFile()
- 适合执行可执行文件spawn()
- 适合流式处理fork()
- 适合创建 Node.js 子进程
根据具体需求选择合适的方法,并注意错误处理和资源管理,可以充分发挥 Node.js 的多进程能力。