Node.js-进程

Node.js可以高效的处理I/O操作,如果处理CPU密集型的任务可能会阻塞事件循环。因此,Node.js允许创建进程,将CPU密集型任务分配给另一个进程处理,释放事件循环。在Node.js中,子进程和父进程能够进行双向通信,并且一定程度上,父进程可以监控子进程。
另一种使用子进程的情况是执行一个外部命令,并在执行结束后,让Node.js获得返回的结果。

执行外部命令

当需要执行一个外部shell命令或者可执行文件时,可以使用child_process模块:

1
2
3
var child_process = require('child_process');
var exec = child_precess.exec;
exec(command, callback);

exec的第一参数是一个字符串,表示准备执行的shell命令,第二个参数是一个回调函数,在命令结束或发生错误时调用,回调函数应有三个参数: error、stdout和stderr。例:

1
2
3
exec('ls', function(error, stdout, stderr){
.....
})

如果出现错误,第一个参数是Error的实例,如果第一个参数不包含错误,第二个参数stdout将会包含命令的输出信息,最后一个参数包含命令的错误输出信息。

生成子进程

child_process.exec()函数虽然能启动进程,但也有一些缺点:

  1. 除了命令行参数和环境变量之外,exec()函数不允许与子进程通信;
  2. 子进程的输出是被缓存的,结果是无法对其进行流操作,可能会耗尽内存。

Node.js的 child_process 模块允许对子进程的启动、终止以及与其进行交互进行精细控制。可以在程序(父进程)中新建一个进程(子进程),一旦启动了新的子进程,Node.js就创建了一个双向通信通道,两个进程可以利用这条通道互相收发字符串形式的数据,父进程可以对子进程施加一些控制、向其发送信号或者强制终止子进程。

创建子进程

基于child_process.spawn函数创建子进程:

1
2
3
var spawn = require('child_process').spawn;

var child = spawn('tail',['-f', './info.txt']);

上面代码创建了一个子进程,通过tail命令监视文件,并且输出的数据被附加到stdout流中,spawn函数会返回一个ChildProcess对象,该对象是一个句柄,封装了实际进程的访问。

监听子进程的输出数据

任何一个子进程句柄都有一个属性stdout,它以流的形式表示子进程的标准输出信息,然后可以在这个流上绑定事件。

1
2
3
4
5
6
7
//在我的js目录下有个同级的info.txt文件,执行下面程序,会输出info.txt的内容
var child_process = require('child_process');
var child = child_process.spawn('tail',['-f', 'info.txt']);

child.stdout.on('data', function(data){
console.log('data from child: ' + data)
})

除了标准输出之外,进程还有一个默认输出流:standard error流,进程通常利用该流输出错误信息。

1
2
3
child.stderr.on('data', function(data){
console.log('tail error output:' + data);
})

向子进程发送数据

除了从子进程的输出流中获取数据之外,父进程也向子进程的标准输入流中写入数据,相当于向子进程发送数据,标准输入流是用ChildProcess.stdin属性来表示的。

子进程也可以使用process.stdin流来监听数据。但是,首先要恢复流,因为在默认情况下,它处于暂停状态。
写一个plus.js文件如下:

1
2
3
4
5
6
7
8
9
10
11
process.stdin.resume();
process.stdin.on('data', function(data){
var number;
try{
number = parseInt(data.toString(), 10);
number += 1;
process.stdout.write(number + '\n');
}catch(err){
process.stderr.write(err.message + '\n');
}
})

直接调用

1
node plus.js 

运行后,程序等待输入,如果输入一个整数按回车键,屏幕上就会显示一个加1后的返回的整数。Ctrl+c 退出应用程序。

创建一Node进程来使用plus.js提供计算服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var spawn = require('child_process').spawn;

//使用node进程创建一个子进程执行plus_one使用程序
var child =spawn('node',['plus_one.js']);

setInterval(function(){
//产生一个小于10的随机数
var number = Math.floor(Math.random()*10);
//将随机数发送给子进程
child.stdin.write(number + '\n');
//获得子进程的相应并打印出来
child.stdout.once('data', function(data){
console.log('child replied to ' + number + ' width: ' + data);
})
}, 1000);

child.stderr.on('data',function(data){
process.stdout.write(data);
})

当子进程退出时获得通知

当子进程退出时,会在父进程上触发一个事件。

1
2
3
4
5
6
7
8
9
10
var spawn = require('child_process').spawn;

var child = spawn('ls', ['-la']);
child.stdout.on('data', function(data){
console.log('data form child: ' + data);
});
//当子进程退出时:
child.on('exit', function(code){
console.log('child process terminated with code ' + code);
})

监听子进程exit事件,当该事件出现,就将子进程终止的相关信息打印至控制台。子进程的退出码会被传递给回调函数,作为其第一个参数。一些程序以非0的退出码表示发生错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var spawn = require('child_process').spawn;

var child = spawn('sleep', ['10']);
setTimeout(function(){
child.kill();
}, 1000)
//当子进程退出时:
child.on('exit', function(code, signal){
if(code){
console.log('child process terminated with code ' + code);
}else if(signal){
console.log('child process terminated with signal ' + signal);
}
})

//输出:child process terminated with signal SIGTERM

向进程发送信号并终止进程

一般而言,可用使用child.kill方法向子进程发送一个信号,默认发送的是SIGTERM信号。

1
2
3
4
5
6
var spawn = require('child_process').spawn;

var child = spawn('sleep', ['10']);
setTimeout(function(){
child.kill();
}, 1000)

还可以将一个标识信号类型的字符串传递给child.kill方法,作为其唯一参数。

1
child.kill('SIGUSR2');

注意,尽管方法名是kill,但是发送的信号却不一定终止进程。在Node中,子进程可以定义一个信号处理程序来重写信号。

1
2
3
process.on('SIGUSR2', function(){
console.log('Got a SIGUSR2 signal');
})

上述代码,为SIGUSR2定义了一个信号处理程序,进程在收到该信号后不会被终止,而是输出了’Got a SIGUSR2 signal’ 。

SIGKILL和SIGSTOP是由操作系统处理的特殊信号,并且进程不能重写其默认行为,即使已经定义了处理程序,它们也会终止进程。