Node基础篇之子进程child_process
kekobin opened this issue · 0 comments
简介
child_process提供生成子进程的能力,主要基于起spawn方法,官方示例:
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
默认情况下,上面的stdin、stdout和stderr的管道在父node.js进程和派生的子进程之间建立。这些管道具有有限的(和特定于平台的)容量。如果子进程在未捕获输出的情况下向stdout写入的数据超过了该限制,则子进程将阻止等待管道缓冲区接受更多数据。这与管壳中管道的行为相同。如果输出不会被使用,请使用{stdio:'ignore'}选项。
创建子进程的方式
- child_process.exec():生成一个shell并在该shell中运行命令,完成后将stdout和stderr传递给回调函数。
- child_process.execfile():与child_process.exec()类似,只是它直接生成命令,默认情况下不首先生成shell。
- child_process.fork():生成一个新的node.js进程并调用一个指定的模块,该模块建立了一个IPC通信通道,允许在父进程和子进程之间发送消息。
- child_process.execsync():将阻止node.js事件循环的child_process.exec()的同步版本。
- child_process.execfilesync():将阻止node.js事件循环的child_process.execfile()的同步版本。
child_process.exec(command[, options][, callback])
其中,options可选参数为:
- cwd:当前工作路径。
- env:环境变量。
- encoding:编码,默认是utf8。
- shell:用来执行命令的shell,unix上默认是/bin/sh,windows上默认是cmd.exe。
- timeout:默认是0。
- killSignal:默认是SIGTERM。
- uid:执行进程的uid。
- gid:执行进程的gid。
- maxBuffer: 标准输出、错误输出最大允许的数据量(单位为字节),如果超出的话,子进程就会被杀死。默认是200*1024
例子:
const exec = require('child_process').exec;
exec('node -v', (error, stdout, stderr) => {
if(error) {
console.error('error: ' + error);
return;
}
console.log('stdout: ' + stdout);
console.log('stderr: ' + typeof stderr);
});
child_process.execFile(file[, args][, options][, callback])
const execFile = require('child_process').execFile;
execFile('node', ['-v'], (error, stdout, stderr) => {
if(error) {
console.error('error: ' + error);
return;
}
console.log('stdout: ' + stdout);
console.log('stderr: ' + typeof stderr);
});
输出结果和上面的exec例子一模一样,只是在于是否创建了shell。
child_process.fork(modulePath[, args][, options])
modulePath:子进程运行的模块。
args参数说明:
- execPath: 用来创建子进程的可执行文件,默认是/usr/local/bin/node。也就是说,你可通过execPath来指定具体的node可执行文件路径。(比如多个node版本)
- execArgv: 传给可执行文件的字符串参数列表。默认是process.execArgv,跟父进程保持一致。
- silent: 默认是false,如果为true,则子进程的stdin、stdout和stderr将通过管道传输到父进程,否则它们将从父进程继承。
- stdio: 如果声明了stdio,则会覆盖silent选项的设置。
fork实例
silent
parent.js
var child_process = require('child_process');
// 例子一:会打印出 output from the child
// 默认情况,silent 为 false,子进程的 stdout 等
// 从父进程继承
child_process.fork('./child.js', {
silent: false
});
// 例子二:不会打印出 output from the silent child
// silent 为 true,子进程的 stdout 等
// pipe 向父进程
child_process.fork('./silentChild.js', {
silent: true
});
// 例子三:打印出 output from another silent child
var child = child_process.fork('./anotherSilentChild.js', {
silent: true
});
child.stdout.setEncoding('utf8');
child.stdout.on('data', function(data){
console.log(data);
});
child.js
console.log('output from the child');
silentChild.js
console.log('output from the silent child');
anotherSilentChild.js
console.log('output from another silent child');
说明:
- slient: false 指的是子进程的stdin、stdout和stderr从父进程继承,也即像父进程那样直接输入输出、报错等;
- slient: true 指的是子进程的stdin、stdout和stderr将通过管道传输到父进程,也就是说子进程中的信息,不能直接输入输出、报错,必须从父进程中显示的接收,在父进程中才能被展示出来。
IPC进程间通信
- parent.js
var child_process = require('child_process');
var child = child_process.fork('./child.js');
child.on('message', function(m){
console.log('message from child: ' + JSON.stringify(m));
});
child.send({from: 'parent'});
- child.js
process.on('message', function(m){
console.log('message from parent: ' + JSON.stringify(m));
});
process.send({from: 'child'});
运行结果:
message from child: {"from":"child"}
message from parent: {"from":"parent"}
execArgv
设置execArgv的目的一般在于,让子进程跟父进程保持相同的执行环境。
比如,父进程指定了--harmony,如果子进程没有指定,那么就要跪了。
child_process.spawn(command[, args][, options])
options参数说明:
- argv0:[String] 这货比较诡异,在uninx、windows上表现不一样。有需要再深究。
- stdio:[Array] | [String] 子进程的stdio。参考这里
- detached:[Boolean] 让子进程独立于父进程之外运行。同样在不同平台上表现有差异。
- shell:[Boolean] | [String] 如果是true,在shell里运行程序。默认是false。(很有用,比如 可以通过 /bin/sh -c xxx 来实现 .exec() 这样的效果)
var spawn = require('child_process').spawn;
var ls = spawn('ls', ['-al'], {
stdio: 'inherit'
});
ls.on('close', function(code){
console.log('child exists with code: ' + code);
});
var spawn = require('child_process').spawn;
// 运行 echo "hello nodejs" | wc
var ls = spawn('bash', ['-c', 'echo "hello nodejs" | wc'], {
stdio: 'inherit',
shell: true
});
ls.on('close', function(code){
console.log('child exists with code: ' + code);
});
错误处理,包含两种场景,这两种场景有不同的处理方式。
场景1:命令本身不存在,创建子进程报错。
场景2:命令存在,但运行过程报错。
// 场景1
child.on('error', (err) => {...
// 场景2
child2.stderr.on('data'...
exec()与execFile()之间的区别
首先,exec() 内部调用 execFile() 来实现,而 execFile() 内部调用 spawn() 来实现。
exec() -> execFile() -> spawn()
其次,execFile() 内部默认将 options.shell 设置为false,exec() 默认不是false。
事件
ChildProcess 实例注册的事件有 error,close,message
- 如果进程不能被衍生(spawn)或者被 killed,error 事件被触发;
- 当子进程使用 process.send() 函数发送信息的时候,message 事件会被触发。这就是父子进程相会交流的方式。
每一个子进程都会得到三个标准的输入输出流,我们可以通过 child.stdin,child.stdout 和 child.stderr 进入。
当那些流关闭之后,使用他们的子进程将会触发 close 事件。这个 close 事件和 exit 事件不同,因为多个子进程可能共享相同的 stdio 流,因此一个子进程退出不代表流关闭了。
// 主进程中
child.stdout.on('data', (data) => {
console.log(`child stdout: ${data}`)
});
child.stderr.on('data', (data) => {
console.error(`stderror ${data}`);
});
示例
可以将密集计算的逻辑放到单独的js文件中,然后再通过fork的方式来计算,等计算完成时再通知主进程计算结果,这样避免主进程繁忙的情况了。
compute.js
const longComputation = () => {
let sum = 0;
for (let i = 0; i < 1e10; i++) {
sum += i;
};
return sum;
};
process.on('message', (msg) => {
const sum = longComputation();
process.send(sum);
});
index.js
const http = require('http');
const { fork } = require('child_process');
const server = http.createServer();
server.on('request', (req, res) => {
if (req.url === '/compute') {
const compute = fork('compute.js');
compute.send('start');
compute.on('message', sum => {
res.end(`Sum is ${sum}`);
});
} else {
res.end('Ok')
}
});
server.listen(3000);
通过child.unref()让父进程退出(动态创建守护进程的方式)
调用child.unref(),将子进程从父进程的事件循环中剔除。于是父进程可以愉快的退出。这里有几个要点
调用child.unref()
设置detached为true
设置stdio为ignore(这点容易忘)
var child_process = require('child_process');
var child = child_process.spawn('node', ['child.js'], {
detached: true,
stdio: 'ignore' // 备注:如果不置为 ignore,那么 父进程还是不会退出
// stdio: 'inherit'
});
child.unref();
信号
SIGINT:interrupt,程序终止信号,通常在用户按下CTRL+C时发出,用来通知前台进程终止进程。
SIGTERM:terminate,程序结束信号,该信号可以被阻塞和处理,通常用来要求程序自己正常退出。shell命令kill缺省产生这个信号。如果信号终止不了,我们才会尝试SIGKILL(强制终止)。
spawn执行python脚本报“ImportError: No module named pandas”
问题的原因是python安装第三方包是通过全局安装的(即sudo安装),在运行时会通过全局的PYTHONPATH去搜索import到的这些第三方包,node是通过process.env加载环境变量的,有可能少了python运行需要的路径(一般node都是$用户启动,而不是#根用户启动),故需要在运行时手动添加上去:
const ls = spawn('python', ['test.py','-u', 'xxx'], {
timeout: 10000,
env: Object.assign({}, process.env, {PYTHONPATH: '/xxx/lib/python2.7/site-packages'})
});
这样,python通过node开启的子进程运行时就找得到它内部引用的第三方包了。
python一般通过pip(类似于npm)装包到具体包版本下的site-packages下,所以要引用到这个目录下的包,必须得把它的路径暴露给全局的环境变量
问题查询集合:
Python 模块搜索路径
Node.js child_process execution of Python script causing errors importing modules
Python won't find installed module when run as child process within Electron app