在Node.js中通过spawn()/exec()/...
生成子进程时,子进程上有一个'close'
和一个'exit'
事件。
这两者之间的区别是什么?你什么时候需要使用什么?
Node.js 0.7.7之前,子进程上只有一个"退出"事件(没有"关闭"事件)。当子进程退出并且所有流(stdin、stdout、stdout)都关闭时,将触发此事件。
在节点0.7.7中,引入了"关闭"事件(请参阅提交)。文件(permalink)目前说:
当子进程的stdio流关闭时,会发出"关闭"事件。这与"退出"事件不同,因为多个进程可能共享相同的stdio流。
如果您只是生成一个程序,并且没有对stdio执行任何特殊操作,则在"退出"后会触发"关闭"事件。如果stdout流通过管道连接到另一个流,则"关闭"事件可能会延迟。因此,这意味着"关闭"事件可以在"退出"事件之后(无限期)延迟
这是否意味着"关闭"事件总是在"退出"之后触发?正如下面的例子所示,答案是否定的。
因此,如果您只对进程终止感兴趣(例如,因为进程拥有独占资源),那么监听"退出"就足够了。如果您不关心程序,只关心它的输入和/或输出,请使用"关闭"事件。
实验:杀死孩子之前先销毁stdio
通过实验(在Node.js v7.2.0中),我发现如果子进程不使用stdio流,那么只有在程序退出后才会触发"关闭"事件:
// The "sleep" command takes no input and gives no output.
cp = require('child_process').spawn('sleep', ['100']);
cp.on('exit', console.log.bind(console, 'exited'));
cp.on('close', console.log.bind(console, 'closed'));
cp.stdin.end();
cp.stdout.destroy();
cp.stderr.destroy();
console.log('Closed all stdio');
setTimeout(function() {
console.log('Going to kill');
cp.kill();
}, 500);
上述程序生成"睡眠"输出:
Closed all stdio
Going to kill
exited null SIGTERM
closed null SIGTERM
当我将第一行更改为只输出的程序时,
// The "yes" command continuously outputs lines with "y"
cp = require('child_process').spawn('yes');
则输出为:
Closed all stdio
exited 1 null
closed 1 null
Going to kill
类似地,当我更改生成一个只从stdin读取的程序时,
// Keeps reading from stdin.
cp = require('child_process').spawn('node', ['-e', 'process.stdin.resume()']);
或者当我从stdin读取并输出到stdout时,
// "cat" without arguments reads from stdin, and outputs to stdout
cp = require('child_process').spawn('cat');
实验:管道程序到另一个,杀死第一个程序
以前的实验是很人为的。下一个实验更为现实:将一个程序管道传输到另一个程序并杀死第一个程序。
// Reads from stdin, output the input to stdout, repeat.
cp = require('child_process').spawn('bash', ['-c', 'while read x ; do echo "$x" ; done']);
cp.on('exit', console.log.bind(console, 'exited'));
cp.on('close', console.log.bind(console, 'closed'));
cpNext = require('child_process').spawn('cat');
cp.stdout.pipe(cpNext.stdin);
setTimeout(function() {
// Let's assume that it has started. Now kill it.
cp.kill();
console.log('Called kill()');
}, 500);
输出:
Called kill()
exited null SIGTERM
closed null SIGTERM
类似地,当第一个程序只从输入读取而从不输出时:
// Keeps reading from stdin, never outputs.
cp = require('child_process').spawn('bash', ['-c', 'while read ; do : ; done']);
当第一个程序在不等待stdin的情况下继续输出时,行为会有所不同,正如下一个实验所示。
实验:将大量输出的管道程序传输到另一个程序,杀死第一个程序
// Equivalent to "yes | cat".
cp = require('child_process').spawn('yes');
cp.on('exit', console.log.bind(console, 'exited'));
cp.on('close', console.log.bind(console, 'closed'));
cpNext = require('child_process').spawn('cat');
cp.stdout.pipe(cpNext.stdin);
setTimeout(function() {
// Let's assume that it has started. Now kill it.
cp.kill();
console.log('Called kill()');
setTimeout(function() {
console.log('Expecting "exit" to have fired, and not "close"');
// cpNext.kill();
// ^ Triggers 'error' event, errno ECONNRESET.
// ^ and does not fire the 'close' event!
// cp.stdout.unpipe(cpNext.stdin);
// ^ Does not appear to have any effect.
// ^ calling cpNext.kill() throws ECONNRESET.
// ^ and does not fire the 'close' event!
cp.stdout.destroy(); // <-- triggers 'close'
cpNext.stdin.destroy();
// ^ Without this, cpNext.kill() throws ECONNRESET.
cpNext.kill();
}, 500);
}, 500);
上述程序输出以下内容,然后退出:
Called kill()
exited null SIGTERM
Expecting "exit" to have fired, and not "close"
closed null SIGTERM
简短的版本是,当子级退出但stdio尚未关闭时,'exit'会发出。"close"在子级退出并且其stdios关闭时发出。
此外,他们有相同的签名。