Q12: child_process 模块提供了哪几种创建子进程的方式?它们的区别是什么?

23 阅读3分钟

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