Child Process: exec, spawn, fork

Модуль child_process позволяет Node.js запускать дочерние процессы — выполнять shell-команды, сторонние программы или другие Node.js-скрипты параллельно с основным процессом.

Зачем нужно

Node.js однопоточный, и тяжёлые CPU-задачи блокируют Event Loop. child_process позволяет вынести такие задачи во внешний процесс: запустить Python-скрипт, выполнить команду git, конвертировать видео через ffmpeg или разделить вычисления между несколькими экземплярами Node. Это основа для реальной параллельности без Worker Threads.

Где используется

  • Запуск shell-команд из Node (git, ffmpeg, imagemagick)
  • Выполнение Python/Ruby-скриптов из Node-бэкенда
  • Параллельная обработка данных через fork (CPU-bound задачи)
  • Build-системы и task-runners (запуск команд сборки)

Основной контент

exec — выполнить команду, получить буфер

const { exec } = require('child_process');

// exec буферизует весь вывод в памяти
exec('ls -la', (error, stdout, stderr) => {
  if (error) {
    console.error(`Ошибка: ${error.message}`);
    return;
  }
  if (stderr) console.error(`stderr: ${stderr}`);
  console.log(stdout);
});

// Промисифицированная версия
const { promisify } = require('util');
const execAsync = promisify(exec);

async function getGitLog() {
  const { stdout } = await execAsync('git log --oneline -5');
  return stdout.trim().split('\n');
}

spawn — запуск с потоковым выводом

const { spawn } = require('child_process');

// spawn не буферизует — данные приходят через Stream
const ls = spawn('ls', ['-la', '/usr']);

ls.stdout.on('data', (data) => {
  process.stdout.write(data); // стримим вывод
});

ls.stderr.on('data', (data) => {
  console.error(`stderr: ${data}`);
});

ls.on('close', (code) => {
  console.log(`Процесс завершился с кодом ${code}`);
});

// Пример: запуск ffmpeg для конвертации
const ffmpeg = spawn('ffmpeg', ['-i', 'input.mp4', 'output.gif']);
ffmpeg.stderr.pipe(process.stderr);

fork — запуск Node.js скрипта с IPC

// worker.js — дочерний скрипт
process.on('message', (msg) => {
  const result = heavyComputation(msg.data);
  process.send({ result });
});

function heavyComputation(n) {
  let sum = 0;
  for (let i = 0; i < n; i++) sum += i;
  return sum;
}

// main.js — основной процесс
const { fork } = require('child_process');

const worker = fork('./worker.js');

worker.send({ data: 1e9 });

worker.on('message', (msg) => {
  console.log('Результат:', msg.result);
  worker.kill;
});

worker.on('error', (err) => console.error(err));

execFile — запуск файла напрямую (без shell)

const { execFile } = require('child_process');

// Безопаснее exec — не запускает shell, нет shell injection
execFile('/usr/bin/node', ['--version'], (error, stdout) => {
  console.log(stdout); // v20.11.0
});

Сравнение методов

Метод Shell Буфер IPC Использование
exec да да нет Короткие команды, небольшой вывод
spawn нет нет (stream) нет Долгие процессы, большой вывод
fork нет нет да Дочерние Node.js скрипты
execFile нет да нет Запуск файлов без shell

Частые ошибки

  • Shell Injection в exec — никогда не передавать пользовательский ввод напрямую в exec; использовать execFile или spawn с массивом аргументов
  • Переполнение буфера в exec — по умолчанию maxBuffer = 1 MB; увеличить через { maxBuffer: 10 * 1024 * 1024 } или использовать spawn
  • Зомби-процессы — не вызвать worker.kill после завершения работы с дочерним процессом
  • Игнорировать код выхода — ненулевой код означает ошибку; всегда проверять close event или error

Связанные темы

Ресурсы


🎓 Источник: Межпроцессовое взаимодействие в Node.js

  • 📅 2018-10-16 · YouTube
  • Тезисы:
    • child_process.fork('./worker.js') возвращает объект Worker — EventEmitter с pid, send, on('message').
    • worker.send(obj) сериализует объект в JSON и шлёт через IPC канал.
    • В воркере process.on('message', ...) и process.send(...) — обратная сторона канала.
    • console.log синхронный — несколько воркеров пишут вперемешку, порядок не гарантирован.
    • cluster — обёртка над child_process; передаёт хендлы серверных сокетов между процессами.
  • Цитата: «Воркеры, которые порождаются кластером, — экземпляры того же самого класса, что и child process, потому что библиотечка cluster это обёртка над child_process.»