ChildProcess关闭、退出事件之间的差异



在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关闭时发出。

此外,他们有相同的签名。

相关内容

  • 没有找到相关文章

最新更新