Method for creating child process in nodejs

  • 2021-10-25 05:42:37
  • OfStack

Directory introduction child process asynchronous creation process synchronous creation process

Brief introduction

main event loop of nodejs is single-threaded, nodejs itself maintains Worker Pool to handle some time-consuming operations, and we can manually create new threads to perform our own tasks by using worker_threads provided by nodejs.

This article introduces a new way to perform nodejs tasks, child process.

child process

lib/child_process. js provides the child_process module, through which we can create child processes.

Note that worker_threads creates child threads and child_process creates child processes.

In the child_process module, processes can be created synchronously or asynchronously. The synchronous creation method simply adds Sync after the asynchronous creation method.

The created process is represented by the ChildProcess class.

Let's look at the definition of ChildProcess:


interface ChildProcess extends events.EventEmitter {
 stdin: Writable | null;
 stdout: Readable | null;
 stderr: Readable | null;
 readonly channel?: Pipe | null;
 readonly stdio: [
  Writable | null, // stdin
  Readable | null, // stdout
  Readable | null, // stderr
  Readable | Writable | null | undefined, // extra
  Readable | Writable | null | undefined // extra
 ];
 readonly killed: boolean;
 readonly pid: number;
 readonly connected: boolean;
 readonly exitCode: number | null;
 readonly signalCode: NodeJS.Signals | null;
 readonly spawnargs: string[];
 readonly spawnfile: string;
 kill(signal?: NodeJS.Signals | number): boolean;
 send(message: Serializable, callback?: (error: Error | null) => void): boolean;
 send(message: Serializable, sendHandle?: SendHandle, callback?: (error: Error | null) => void): boolean;
 send(message: Serializable, sendHandle?: SendHandle, options?: MessageOptions, callback?: (error: Error | null) => void): boolean;
 disconnect(): void;
 unref(): void;
 ref(): void;

 /**
  * events.EventEmitter
  * 1. close
  * 2. disconnect
  * 3. error
  * 4. exit
  * 5. message
  */
 ...
 }

You can see that ChildProcess is also an EventEmitter, so it can send and receive event.

ChildProcess can receive five kinds of event, which are close, disconnect, error, exit and message.

The disconnect event is triggered when subprocess. disconnect () in the parent process or process. disconnect () in the child process is called.

error events are triggered when a process cannot be created, an kill process cannot be created, and a message to a child process fails.

The exit event is triggered when the child process ends.

The close event is triggered when the stdio stream of a child process is closed. Note that the close event is different from the exit event because multiple processes may share the same stdio, so sending the exit event does not necessarily trigger the close event.

Look at an example of close and exit:


const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);

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

ls.on('close', (code) => {
 console.log(` Child process usage code  $[code]  Close all  stdio`);
});

ls.on('exit', (code) => {
 console.log(` Child process usage code  $[code]  Quit `);
});

Finally, the message event is triggered when a child process sends a message using process. send ().

There are several standard stream properties in ChildProcess, namely stderr, stdout, stdin, and stdio.

stderr, stdout, and stdin are easy to understand and are standard error, standard output, and standard input, respectively.

Let's look at the use of an stdout:


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

const subprocess = spawn('ls');

subprocess.stdout.on('data', (data) => {
 console.log(` Received data block  ${data}`);
});

stdio is actually a collection of stderr, stdout, stdin:


readonly stdio: [
  Writable | null, // stdin
  Readable | null, // stdout
  Readable | null, // stderr
  Readable | Writable | null | undefined, // extra
  Readable | Writable | null | undefined // extra
 ];

Where stdio [0] represents stdin, stdio [1] represents stdout, and stdio [2] represents stderr.

stdin, stdout, and stderr will be null if the three standard streams are set to values other than pipe when the child process is created through stdio.

Let's look at an example of using stdio:


const assert = require('assert');
const fs = require('fs');
const child_process = require('child_process');

const subprocess = child_process.spawn('ls', {
 stdio: [
 0, //  Using the parent process's  stdin  Used for child processes. 
 'pipe', //  Put the child process's  stdout  Piped to parent process   . 
 fs.openSync('err.out', 'w') //  Put the child process's  stderr  Directed to 1 File. 
 ]
});

assert.strictEqual(subprocess.stdio[0], null);
assert.strictEqual(subprocess.stdio[0], subprocess.stdin);

assert(subprocess.stdout);
assert.strictEqual(subprocess.stdio[1], subprocess.stdout);

assert.strictEqual(subprocess.stdio[2], null);
assert.strictEqual(subprocess.stdio[2], subprocess.stderr);

Normally, the parent process maintains a reference count to the child process, and the parent process will exit only after the child process exits.

This reference is ref, which allows the parent process to exit independently of the child process if the unref method is called.


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

const subprocess = spawn(process.argv[0], ['child_program.js'], {
 detached: true,
 stdio: 'ignore'
});

subprocess.unref();

Finally, let's look at how to send messages through ChildProcess:


subprocess.send(message[, sendHandle[, options]][, callback])

Where message is the message to be sent, and callback is the callback after sending the message.

sendHandle is special, it can be an TCP server or an socket object by passing these handle to child processes. The child process will pass the handle to the Callback function in the message event so that it can be processed in the child process.

Let's look at an example of passing TCP server. First, look at the main process:


const subprocess = require('child_process').fork('subprocess.js');

//  Open  server  Object and send the handle. 
const server = require('net').createServer();
server.on('connection', (socket) => {
 socket.end(' Handled by parent process ');
});
server.listen(1337, () => {
 subprocess.send('server', server);
});

Look at the sub-process again:


process.on('message', (m, server) => {
 if (m === 'server') {
 server.on('connection', (socket) => {
 socket.end(' Handled by child processes ');
 });
 }
});

You can see that the child process receives server handle and listens for connection events in the child process.

Let's look at an example of passing an socket object:


onst { fork } = require('child_process');
const normal = fork('subprocess.js', ['normal']);
const special = fork('subprocess.js', ['special']);

//  Open  server And send  socket  To the child process. 
//  Use  `pauseOnConnect`  Prevent  socket  Read before being sent to a child process. 
const server = require('net').createServer({ pauseOnConnect: true });
server.on('connection', (socket) => {

 //  Special priority. 
 if (socket.remoteAddress === '74.125.127.100') {
 special.send('socket', socket);
 return;
 }
 //  Normal priority. 
 normal.send('socket', socket);
});
server.listen(1337);

Contents of subprocess. js:


const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);

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

ls.on('close', (code) => {
 console.log(` Child process usage code  $[code]  Close all  stdio`);
});

ls.on('exit', (code) => {
 console.log(` Child process usage code  $[code]  Quit `);
});
0

The main process creates two subprocess, one for special priority and one for ordinary priority.

Asynchronous creation process

The child_process module has four ways to create processes asynchronously, namely child_process. spawn (), child_process. fork (), child_process. exec (), and child_process. execFile ().

First look at a definition of each method:


const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);

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

ls.on('close', (code) => {
 console.log(` Child process usage code  $[code]  Close all  stdio`);
});

ls.on('exit', (code) => {
 console.log(` Child process usage code  $[code]  Quit `);
});
1

Among them, child_process. spawn is the foundation, which will generate a new process asynchronously. Other fork, exec and execFile are all generated based on spawn.

fork generates a new Node. js process.

exec and execFile execute new commands as new processes with callback. The difference between them is that in the windows environment, if you want to execute. bat or. cmd files, you can't execute them without shell terminals. At this time, it can only be started with exec. execFile cannot be executed.

Or you can use spawn.

Let's look at an example of using spawn and exec in windows:


const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);

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

ls.on('close', (code) => {
 console.log(` Child process usage code  $[code]  Close all  stdio`);
});

ls.on('exit', (code) => {
 console.log(` Child process usage code  $[code]  Quit `);
});
2

const { exec, spawn } = require('child_process');
exec('my.bat', (err, stdout, stderr) => {
 if (err) {
 console.error(err);
 return;
 }
 console.log(stdout);
});

//  Scripts with spaces in file names: 
const bat = spawn('"my script.cmd"', ['a', 'b'], { shell: true });
//  Or: 
exec('"my script.cmd" a b', (err, stdout, stderr) => {
 // ...
});

Synchronize the creation process

Synchronized creation processes can use child_process. spawnSync (), child_process. execSync (), and child_process. execFileSync (). Synchronized methods block the Node. js event loop, pausing execution of any other code until the child process exits.

Usually, for some script tasks, it is common to use synchronous creation process.


Related articles: