简介

nodejs的main event loop是单线程的,nodejs自身也保护着Worker Pool用来解决一些耗时的操作,咱们还能够通过应用nodejs提供的worker_threads来手动创立新的线程来执行本人的工作。

本文将会介绍一种新的执行nodejs工作的形式,child process。

child process

lib/child_process.js提供了child_process模块,通过child_process咱们能够创立子过程。

留神,worker_threads创立的是子线程,而child_process创立的是子过程。

在child_process模块中,能够同步创立过程也能够异步创立过程。同步创立形式只是在异步创立的办法前面加上Sync。

创立进去的过程用ChildProcess类来示意。

咱们看下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         */        ...    }

能够看到ChildProcess也是一个EventEmitter,所以它能够发送和承受event。

ChildProcess能够接管到event有5种,别离是close,disconnect,error,exit和message。

当调用父过程中的 subprocess.disconnect() 或子过程中的 process.disconnect() 后会触发 disconnect 事件。

当呈现无奈创立过程,无奈kill过程和向子过程发送音讯失败的时候都会触发error事件。

当子过程完结后时会触发exit事件。

当子过程的 stdio 流被敞开时会触发 close 事件。 留神,close事件和exit事件是不同的,因为多个过程可能共享同一个stdio,所以发送exit事件并不一定会触发close事件。

看一个close和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(`子过程应用代码 ${code} 敞开所有 stdio`);});ls.on('exit', (code) => {  console.log(`子过程应用代码 ${code} 退出`);});

最初是message事件,当子过程应用process.send() 发送音讯的时候就会被触发。

ChildProcess中有几个规范流属性,别离是stderr,stdout,stdin和stdio。

stderr,stdout,stdin很好了解,别离是规范谬误,规范输入和规范输出。

咱们看一个stdout的应用:

const { spawn } = require('child_process');const subprocess = spawn('ls');subprocess.stdout.on('data', (data) => {  console.log(`接管到数据块 ${data}`);});

stdio实际上是stderr,stdout,stdin的汇合:

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

其中stdio[0]示意的是stdin,stdio[1]示意的是stdout,stdio[2]示意的是stderr。

如果在通过stdio创立子过程的时候,这三个规范流被设置为除pipe之外的其余值,那么stdin,stdout和stderr将为null。

咱们看一个应用stdio的例子:

const assert = require('assert');const fs = require('fs');const child_process = require('child_process');const subprocess = child_process.spawn('ls', {  stdio: [    0, // 应用父过程的 stdin 用于子过程。    'pipe', // 把子过程的 stdout 通过管道传到父过程 。    fs.openSync('err.out', 'w') // 把子过程的 stderr 定向到一个文件。  ]});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);

通常状况下父过程中保护了一个对子过程的援用计数,只有在当子过程退出之后父过程才会退出。

这个援用就是ref,如果调用了unref办法,则容许父过程独立于子过程退出。

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

最初,咱们看一下如何通过ChildProcess来发送音讯:

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

其中message就是要发送的音讯,callback是发送音讯之后的回调。

sendHandle比拟非凡,它能够是一个TCP服务器或socket对象,通过将这些handle传递给子过程。子过程将会在message事件中,将该handle传递给Callback函数,从而能够在子过程中进行解决。

咱们看一个传递TCP server的例子,首先看主过程:

const subprocess = require('child_process').fork('subprocess.js');// 关上 server 对象,并发送该句柄。const server = require('net').createServer();server.on('connection', (socket) => {  socket.end('由父过程解决');});server.listen(1337, () => {  subprocess.send('server', server);});

再看子过程:

process.on('message', (m, server) => {  if (m === 'server') {    server.on('connection', (socket) => {      socket.end('由子过程解决');    });  }});

能够看到子过程接管到了server handle,并且在子过程中监听connection事件。

上面咱们看一个传递socket对象的例子:

onst { fork } = require('child_process');const normal = fork('subprocess.js', ['normal']);const special = fork('subprocess.js', ['special']);// 开启 server,并发送 socket 给子过程。// 应用 `pauseOnConnect` 避免 socket 在被发送到子过程之前被读取。const server = require('net').createServer({ pauseOnConnect: true });server.on('connection', (socket) => {  // 非凡优先级。  if (socket.remoteAddress === '74.125.127.100') {    special.send('socket', socket);    return;  }  // 一般优先级。  normal.send('socket', socket);});server.listen(1337);

subprocess.js的内容:

process.on('message', (m, socket) => {  if (m === 'socket') {    if (socket) {      // 查看客户端 socket 是否存在。      // socket 在被发送与被子过程接管这段时间内可被敞开。      socket.end(`申请应用 ${process.argv[2]} 优先级解决`);    }  }});

主过程创立了两个subprocess,一个解决非凡的优先级, 一个解决一般的优先级。

异步创立过程

child_process模块有4种形式能够异步创立过程,别离是child_process.spawn()、child_process.fork()、child_process.exec() 和 child_process.execFile()。

先看一个各个办法的定义:

child_process.spawn(command[, args][, options])child_process.fork(modulePath[, args][, options])child_process.exec(command[, options][, callback])child_process.execFile(file[, args][, options][, callback])

其中child_process.spawn是根底,他会异步的生成一个新的过程,其余的fork,exec和execFile都是基于spawn来生成的。

fork会生成新的Node.js 过程。

exec和execFile是以新的过程执行新的命令,并且带有callback。他们的区别就在于在windows的环境中,如果要执行.bat或者.cmd文件,没有shell终端是执行不了的。这个时候就只能以exec来启动。execFile是无奈执行的。

或者也能够应用spawn。

咱们看一个在windows中应用spawn和exec的例子:

// 仅在 Windows 上。const { spawn } = require('child_process');const bat = spawn('cmd.exe', ['/c', 'my.bat']);bat.stdout.on('data', (data) => {  console.log(data.toString());});bat.stderr.on('data', (data) => {  console.error(data.toString());});bat.on('exit', (code) => {  console.log(`子过程退出,退出码 ${code}`);});
const { exec, spawn } = require('child_process');exec('my.bat', (err, stdout, stderr) => {  if (err) {    console.error(err);    return;  }  console.log(stdout);});// 文件名中蕴含空格的脚本:const bat = spawn('"my script.cmd"', ['a', 'b'], { shell: true });// 或:exec('"my script.cmd" a b', (err, stdout, stderr) => {  // ...});

同步创立过程

同步创立过程能够应用child_process.spawnSync()、child_process.execSync() 和 child_process.execFileSync() ,同步的办法会阻塞 Node.js 事件循环、暂停任何其余代码的执行,直到子过程退出。

通常对于一些脚本工作来说,应用同步创立过程会比拟罕用。

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/nodejs-childprocess/

本文起源:flydean的博客

欢送关注我的公众号:「程序那些事」最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!