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