Node.js 面试题详细答案 - Q12
Q12: child_process 模块提供了哪几种创建子进程的方式?它们的区别是什么?
child_process 模块概述
child_process 模块提供了创建子进程的能力,支持四种不同的创建方式。
四种创建方式
1. spawn - 启动新进程
const { spawn } = require('child_process')
// 启动新进程
const child = spawn('ls', ['-la'])
child.stdout.on('data', (data) => {
console.log('输出:', data.toString())
})
child.stderr.on('data', (data) => {
console.error('错误:', data.toString())
})
child.on('close', (code) => {
console.log(`进程退出,代码: ${code}`)
})
2. exec - 执行 shell 命令
const { exec } = require('child_process')
// 执行 shell 命令
exec('ls -la', (error, stdout, stderr) => {
if (error) {
console.error('执行错误:', error)
return
}
console.log('输出:', stdout)
if (stderr) {
console.error('错误:', stderr)
}
})
3. execFile - 执行可执行文件
const { execFile } = require('child_process')
// 执行可执行文件
execFile('node', ['--version'], (error, stdout, stderr) => {
if (error) {
console.error('执行错误:', error)
return
}
console.log('Node.js 版本:', stdout)
})
4. fork - 创建 Node.js 子进程
const { fork } = require('child_process')
// 创建 Node.js 子进程
const child = fork('./worker.js')
child.on('message', (msg) => {
console.log('收到子进程消息:', msg)
})
child.send({ type: 'task', data: 'Hello Worker' })
详细对比
1. spawn vs exec
const { spawn, exec } = require('child_process')
// spawn - 流式处理
function useSpawn() {
const child = spawn('find', ['.', '-name', '*.js'])
child.stdout.on('data', (data) => {
console.log('找到文件:', data.toString().trim())
})
child.on('close', (code) => {
console.log('搜索完成')
})
}
// exec - 一次性执行
function useExec() {
exec('find . -name "*.js"', (error, stdout, stderr) => {
if (error) {
console.error('执行错误:', error)
return
}
console.log('所有文件:', stdout)
})
}
2. exec vs execFile
const { exec, execFile } = require('child_process')
// exec - 通过 shell 执行
exec('echo $PATH', (error, stdout, stderr) => {
console.log('PATH 环境变量:', stdout)
})
// execFile - 直接执行可执行文件
execFile('echo', ['Hello World'], (error, stdout, stderr) => {
console.log('输出:', stdout)
})
3. fork 特殊用法
const { fork } = require('child_process')
// 创建计算密集型子进程
const worker = fork('./calculator.js')
worker.on('message', (result) => {
console.log('计算结果:', result)
})
// 发送计算任务
worker.send({
type: 'calculate',
operation: 'fibonacci',
number: 40,
})
// calculator.js
process.on('message', (msg) => {
if (msg.type === 'calculate') {
const result = fibonacci(msg.number)
process.send(result)
}
})
function fibonacci(n) {
if (n <= 1) return n
return fibonacci(n - 1) + fibonacci(n - 2)
}
实际应用场景
1. 文件处理
const { spawn } = require('child_process')
const fs = require('fs')
// 使用 spawn 处理大文件
function processLargeFile(inputFile, outputFile) {
const child = spawn('grep', ['error', inputFile])
const writeStream = fs.createWriteStream(outputFile)
child.stdout.pipe(writeStream)
child.on('close', (code) => {
console.log('文件处理完成')
})
}
2. 系统命令执行
const { exec } = require('child_process')
// 执行系统命令
function getSystemInfo() {
exec('uname -a', (error, stdout, stderr) => {
if (error) {
console.error('获取系统信息失败:', error)
return
}
console.log('系统信息:', stdout)
})
}
// 执行多个命令
function runMultipleCommands() {
const commands = ['ls -la', 'ps aux', 'df -h']
commands.forEach((cmd) => {
exec(cmd, (error, stdout, stderr) => {
console.log(`命令: ${cmd}`)
console.log('输出:', stdout)
})
})
}
3. 进程间通信
const { fork } = require('child_process')
// 主进程
const worker = fork('./data-processor.js')
// 发送数据到子进程
const data = [1, 2, 3, 4, 5]
worker.send({ type: 'process', data })
// 接收处理结果
worker.on('message', (result) => {
console.log('处理结果:', result)
})
// data-processor.js
process.on('message', (msg) => {
if (msg.type === 'process') {
const result = msg.data.map((x) => x * 2)
process.send({ type: 'result', data: result })
}
})
错误处理
1. spawn 错误处理
const { spawn } = require('child_process')
const child = spawn('nonexistent-command')
child.on('error', (error) => {
console.error('启动进程失败:', error)
})
child.stderr.on('data', (data) => {
console.error('进程错误:', data.toString())
})
child.on('close', (code) => {
if (code !== 0) {
console.error(`进程异常退出,代码: ${code}`)
}
})
2. exec 错误处理
const { exec } = require('child_process')
exec('ls /nonexistent', (error, stdout, stderr) => {
if (error) {
console.error('执行错误:', error.message)
console.error('退出代码:', error.code)
return
}
if (stderr) {
console.error('标准错误:', stderr)
}
console.log('输出:', stdout)
})
性能对比
1. 内存使用
const { spawn, exec, fork } = require('child_process')
// spawn - 低内存使用
function useSpawn() {
const child = spawn('cat', ['large-file.txt'])
child.stdout.on('data', (chunk) => {
// 流式处理,内存使用低
process.stdout.write(chunk)
})
}
// exec - 高内存使用
function useExec() {
exec('cat large-file.txt', (error, stdout, stderr) => {
// 一次性加载所有数据到内存
console.log(stdout)
})
}
2. 执行速度
const { spawn, exec } = require('child_process')
// 测试执行速度
function testSpeed() {
const start = Date.now()
// spawn 方式
const child = spawn('echo', ['Hello World'])
child.on('close', () => {
console.log('spawn 耗时:', Date.now() - start)
})
// exec 方式
exec('echo "Hello World"', () => {
console.log('exec 耗时:', Date.now() - start)
})
}
最佳实践
1. 选择合适的创建方式
const { spawn, exec, execFile, fork } = require('child_process')
// 处理大文件 - 使用 spawn
function processLargeFile() {
const child = spawn('grep', ['pattern', 'large-file.txt'])
// 流式处理,内存效率高
}
// 执行简单命令 - 使用 exec
function executeSimpleCommand() {
exec('ls -la', (error, stdout, stderr) => {
// 简单直接
})
}
// 执行可执行文件 - 使用 execFile
function executeExecutable() {
execFile('./my-script.sh', ['arg1', 'arg2'], (error, stdout, stderr) => {
// 更安全,不通过 shell
})
}
// Node.js 子进程 - 使用 fork
function createNodeWorker() {
const worker = fork('./worker.js')
// 进程间通信
}
2. 安全考虑
const { exec, execFile } = require('child_process')
// 不安全的 exec
function unsafeExec(userInput) {
exec(`ls ${userInput}`, (error, stdout, stderr) => {
// 容易受到命令注入攻击
})
}
// 安全的 execFile
function safeExecFile(userInput) {
execFile('ls', [userInput], (error, stdout, stderr) => {
// 更安全,参数被正确转义
})
}
总结
- spawn:启动新进程,流式处理,内存效率高
- exec:执行 shell 命令,一次性返回结果,简单易用
- execFile:执行可执行文件,不通过 shell,更安全
- fork:创建 Node.js 子进程,支持进程间通信
- 选择原则:根据需求选择合适的方式
- 安全考虑:避免命令注入,使用 execFile 替代 exec
- 性能优化:大文件处理使用 spawn,简单命令使用 exec