Node.js 中 exec、fork 和 spawn 的区别是什么?

Node.js 中 exec 用于执行 shell 命令,fork 启动子进程运行 Node.js 脚本,而 spawn 创建新进程来运行可执行文件。它们各自有不同的数据传输机制和通信能力。

Node.js 困难 子进程 child_process exec

execforkspawn 是 Node.js 的 child_process 模块用来创建子进程的方法,各有不同的机制和使用场景。区别主要在进程创建方式、数据传输机制、通信能力和适用性上。

exec

  • 用途:执行 shell 命令字符串(如 echo "hello" | grep h),在新启动的 shell 进程中运行命令。
  • 核心特性
    • 使用缓冲区收集所有输出(stdout 和 stderr),通过回调一次性返回给父进程。
    • 输出量默认可达 200KB;超出会导致错误,不建议处理大数据。
    • 可设置 timeout 超时值,超时自动终止子进程。
  • 优点: 适合快速的小批量命令操作,需要等待完整输出场景。
  • 语法举例:
    const { exec } = require('child_process');
      
    exec('ls -l', { timeout: 3000 }, (error, stdout, stderr) => {
      if (error) {
        console.error(`错误输出: ${error}`);
        return;
      }
      console.log(stdout);
    });
    

spawn

  • 用途: 直接启动新进程运行可执行文件或程序(如二进制文件或 .js 文件),不依赖 shell。
  • 核心特性
    • 使用流(stdin, stdout, stderr)逐步交互数据,支持实时流输入/输出。
    • 无输出数据大小限制;适合大数据量场景(如处理文件或长时间输出)。
    • 不支持内置 IPC 通信(父子进程需自定义协议处理消息),也不提供 timeout。
    • 是所有衍生基础;底层实现用于任意外部程序(甚至可运行 python script.py)。
  • 优点: 高效的流式操作;适用于大流量、低延迟任务(如数据库导出或实时日志)。
  • 语法举例:
    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}`);
    });
    

fork

  • 用途: 专门启动另一 Node.js 脚本,便于分离运行 JavaScript 代码及通信。
  • 核心特性:
    • 基于 spawn 实现:等同于 spawn('node', [scriptPath]),但自动建立双工 IPC通道用于通信。
    • 支持父子进程消息传递:子进程通过 process.on('message', fn) 监听和 process.send(data) 发送 JSON-serializable 对象。
    • 数据可流但无限制;IPC channel允许灵活状态通信(如集群架构中 worker 任务同步)。
  • 优点: 内建 IPC 优势;适合分离复杂逻辑的任务线程(如Web服务器集群或实时多节点任务)。
  • 语法举例: 父进程文件
    const { fork } = require('child_process');
      
    const child = fork('./worker.js');
    child.on('message', (msg) => {
      console.log(`父接收: ${msg.data}`);
    });
    child.send({ command: 'start', count: 5 });  // JSON对象发送
    

    子进程 (worker.js)

    process.on('message', (msg) => {
      console.log(`子收到: ${msg.command}`);
      process.send({ data: `处理完毕: ${msg.count}` }); // 回复消息
    });
    

主要区别总结

  1. 数据传输机制:
    • spawnfork 使用流,处理无限制数据的实时输入输出;
    • exec 通过内存缓冲区存储完整静态输出;仅限 <=200KB。
  2. 通信能力:
    • fork 内置专用于 JSON结构的 IPC(支持父子消息传递)。
    • spawn需自定义流协议;exec无内置通信模式。
  3. 启动对象差异:
    • fork: 唯一支持 Node.js脚本作为独立JavaScript 实例运行的子进程;
    • spawn: 支持任意可执行程序作为子进程(包括系统命令或 .js 文件须指定 node 命令作为入口);
    • exec: 指定字符串形式的完整 shell命令;不适合运行 .js 文件脚本或二进制直接文件操作。
  4. 其它特性:
    • exec和衍生execFile有可设超时可调参数 timeout,自动停止长期进程;
    • spawn 及 fork 中无 timeout内建特性,父进程必须主动管理终止(如 kill或时间控制函数显式判断响应超时状态)。
    • fork 操作可重用Node.js共享 V8 实例;比单独创建新节点进程资源开销稍高。

应⽤选择指南

  • Node.js 场景: 需执行另一 .js脚本并频繁数据消息时用 fork
  • 大流量外部程序/无固定数据边界场景: 适用 spawn进行灵活流控处理。
  • 简短的 shell命令仅作查询一次性回调时优先 exec
    实际开发中多数函数逻辑基于 spawn底层机制调用选参数格式优化选择任务。