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после завершения работы с дочерним процессом - Игнорировать код выхода — ненулевой код означает ошибку; всегда проверять
closeevent или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.»