Node.js Child Process Management and Inter-Process Communication
Core Concepts
Node.js operates in a single-threaded manner, which underutilizes modern multi-core processors. To address this limitation, the child_process module was introduced to enable process creation and task delegation across multiple processes.
The child_process module introduces the ChildProcess class, representing the interface between parent and child processes. Both the main process and spawned processes are instances of ChildProcess objects.
Key aspects of ChildProcess objects include events, methods, and properties:
Events:
message: Triggered when thesend()method transmits dataerror: Emitted when errors occur within the subprocessexit: Fired when the subprocess terminates, providing code and signal parametersclose: Emitted when all stdio streams terminatedisconnect: Triggered whendisconnect()is called
Methods:
kill([signal]): Sends termination signals to child processessend(message, [sendHandle]): Transmits messages to handlesdisconnect(): Closes IPC channels between processes
Properties:
stdin: Writable input streamstdout: Readable output streamstderr: Readable error output streampid: Process identifierconnected: Boolean indicating connection status
Practical Implementation
exec() - Execute System Commands in Separate Processes
The exec() function executes commands within a subshell environment, supporting binaries, shell scripts, Python programs, and batch files.
const childProcess = require('child_process');
const config = {maxBuffer: 100 * 1024, encoding: 'utf8', timeout: 5000};
const subprocess = childProcess.exec('ls -la', config,
function (error, stdout, stderr) {
if (error) {
console.error(error.stack);
console.error('Error Code: ' + error.code);
console.error('Error Signal: ' + error.signal);
}
console.log('Output: \n' + stdout);
if (stderr.length) {
console.error('Errors: ' + stderr);
}
});
subprocess.on('exit', function (code) {
console.log('Terminated with exit code: ' + code);
});
execFile() - Execute Executable Files
Unlike exec(), execFile() bypasses subshell execution and requires direct binary executable files. Shell scripts and batch files cannot be executed through this method.
const childProcess = require('child_process');
const settings = {maxBuffer: 100 * 1024, encoding: 'UTF-16', timeout: 7000};
const subprocess = childProcess.execFile('curl', ['-s', 'https://api.github.com'],
settings, function (error, stdout, stderr) {
if (error) {
console.error(error.stack);
console.error('Error Code: ' + error.code);
console.error('Error Signal: ' + error.signal);
}
console.log('Response: \n' + stdout.toString());
if (stderr.length) {
console.error('Errors: ' + stderr.toString());
}
});
subprocess.on('exit', function (code) {
console.log('Subprocess terminated with code: ' + code);
});
spawn() - Generate Processes in Separate Node.js Instances
The spawn() function creates processes with connected stdio pipes, enabling real-time streaming rather than waiting for complete execution.
const { spawn } = require('child_process');
const configuration = {
env: {username: 'developer'},
detached: false,
stdio: ['pipe', 'pipe', 'pipe']
};
const subprocess = spawn('ps', ['-aux']);
subprocess.stdout.on('data', function(data) {
console.log(data.toString());
});
subprocess.stderr.on('data', function(data) {
console.log(data.toString());
});
subprocess.on('exit', function(code) {
console.log('Child process exited with code', code);
});
Fork - Process Derivation Implementation
Forking enables execution of Node.js modules in separate V8 engine instances, facilitatnig parallel service operations. Each forked process consumes approximately 10MB memory, making it suitable for long-running processes.
const childProcess = require('child_process');
const options = {
env: {username: 'server-admin'},
encoding: 'utf8'
};
function createWorker() {
const worker = childProcess.fork('worker.js', [], options);
worker.on('message', function(message) {
console.log('Received: ' + message);
});
return worker;
}
function dispatchTask(worker, task) {
console.log("Submitting: " + task);
worker.send({action: task});
}
const worker1 = createWorker();
const worker2 = createWorker();
const worker3 = createWorker();
dispatchTask(worker1, "processData");
dispatchTask(worker2, "generateReport");
dispatchTask(worker3, "backupFiles");
process.on('message', function(message) {
let result = {};
switch (message.action) {
case 'processData':
result = ["processed", "cleaned", "validated"];
break;
case 'generateReport':
result = ["summary", "analytics", "charts"];
break;
case 'backupFiles':
result = ["documents", "images", "databases"];
break;
}
process.send(result);
});
This implementation demonstrates creating multiple subprocesses where the main process sends tasks to workers, and workers respond with results.