Node.js多进程的实现 使用child_process模块管理外部进程

 2015年06月03日    3265     声明


Node.js事件驱动和非阻塞I/O的特性,使其在高效处理I/O方面有较大优势。也正由于这些特性,Node在某些类型的业务处理上并不擅长。比如处理一个计算量较大的CPU密集型业务时,就可能会造成事件阻塞。这时可以使用child_process模块启动一个子进程来处理这一任务,从而释放事件循环。子进程查模块还可以用来执行一个外部命令(如:Linux命令、脚本等)。

在当前Node.js的版本(v0.12.4)中,child_process模块提供了4个异步方法可以用来创建子进程:execexecFileforkspawn,这些方法中除fork方法外都提供了同步版本的方法。本文将结合一些应用场景介绍child_process模块的用户。

  1. 执行外部命令
  2. 创建子进程
  3. 从可执行程序启动子进程
  4. 运行Node模块
  5. 进程控制与中止


1. 执行外部命令:child_process.exec(command[, options], callback)

1.1 使用示例

执行外部命令可以使用exec方法,与spawn方法相比,exec方法更为便捷,exec提供一个回调函数,可以更方便的处理命令执行结果的。示例哪下:

var exec = require('child_process').exec;

exec('cat *.js bad_file | wc -l',
  function (error, stdout, stderr) {
    console.log('stdout: ' + stdout);
    console.log('stderr: ' + stderr);
    if (error !== null) {
      console.log('exec error: ' + error);     //没有错误产生,所以本行无输出
    }
});

在上面代码中,执行cat *.js bad_file | wc -l命令,用于统计当前目录下所有“.js”文件和名为“bad_file”文件的内容行数,由于“bad_file”文件不存在,因此会产生一个"stderr"。执行结果如下:

stdout:      548
stderr: cat: bad_file: No such file or directory

1.2 参数说明

exec 命令有三个参数,command和callbakc为必传参数,options为可选参数:

  • command 要执行的shell命令。字符串类型
  • options 可选参数对象
    • cwd 执行命令的工作目录,未设置时为当前目录。字符串类型
    • env 传递给子进程的环境变量,默认值null,为null时会继承父进程的环境变量
    • encoding 子进程输出的编码格式,默认值: 'utf8'
    • shell 执行命令的shell类型,默认值:UNIX为 '/bin/sh' on UNIX, Windows为 'cmd.exe'
    • timeout 执行命令的超时时间,单位为毫秒, 默认值:0
    • maxBuffer stdout流和stderr流的最大容量,单为字节,超出后子进程会中止。默认值: 200*1024
    • killSignal 超时或超出缓存容量发送到子进程的信号。默认值:'SIGTERM'(进程中止),此信号与UNIX信号一致。
    • uid 执行进程用户的uid
    • gid 执行进程的gid
  • callback 子进程执行完毕的回调函数
    • error Error类型
    • stdout 标准备输出,Buffer类型
    • stderr 标准报错,Buffer类型
  • 返回值: ChildProcess 对象


child_process 模块允许你对子进程进行更加精细的控制,如子进程的启动、终止、与其进行通信等。除exec方法外,child_process 模块中的其它方法都可以实现父子进程间的通信。子进程启动后,就创建了一个双向的通信通道,进程之间可以利用这个通道相互收发一些字符串形式的数据。父进程还可以对子进程发送一些控,如发送信号、终止进程等。


2. 创建子进程:child_process.spawn(command[, args][, options])

可以使用exec方法启动外部进程,并在进程结束时调用回调函数。exec方法使用简单,但也会有一些缺点。

  • 除了命令行参数和环境变量外,exec方法不允许与子进程通信。
  • 子进程的输出是被缓存后输出的,所以无法进行流处理,也可能引起内存耗尽的问题。

2.1 使用示例

spawn方法用于从执行的命令创建一个新进程,命令的参数被做为数组传入,忽略参数时将传入一个空数组。

var child_process = require('child_process');
var child = child_process.spawn('tail', ['-f', '/var/log/system.log']);

以上命令用于监控系统日志,为了能看到进程的输出数据,我们还需要监听一些进程的事件。

//打印子进程的输出数据
child.stdout.on('data', function (data) {
  console.log('stdout: ' + data);
});

//监听子进程的错误流数据
child.stderr.on('data', function (data) {
  console.log('stderr: ' + data);
});

//监听子进程的退出事件
child.on('close', function (code) {
  console.log('子进程退出,code:' + code);
});

2.2 参数说明

  • command 要执行的shell命令。字符串类型
  • args 要执行命令的参数列表。数组类型
  • options 可选参数对象
    • cwd 执行命令的工作目录,未设置时为当前目录。字符串类型
    • env 传递给子进程的环境变量,默认值null,为null时会继承父进程的环境变量
    • stdio 子进程stdio配置。数组或字符串类型
      • 'pipe' - ['pipe', 'pipe', 'pipe'], 默认值
      • 'ignore' - ['ignore', 'ignore', 'ignore']
      • 'inherit' - [process.stdin, process.stdout, process.stderr] or [0,1,2]
    • customFds 数组类型(使用示例
    • detached 子进程是否有群组最高权限, Boolean类型。(使用示例
    • uid 执行进程用户的uid
    • gid 执行进程的gid
  • 返回值: ChildProcess 对象


3. 从可执行程序启动子进程:child_process.execFile(file[, args][, options][, callback])

execFile方法用于从一个可执行程序启动一个子进程。与exec相比,execFile不启动独立的shell,因此更加轻量级。 execFile也可以用于执行命令。

3.1 使用示例

创建.sh文件,文件内容cat *.js bad_file | wc -l,执行chmod +x sh命令,为文件添加可执行属性。使用execFile文件创建子进程,示例如下:

var child_process = require('child_process');

child_process.execFile('./sh', function (error, stdout, stderr) {
    console.log('stdout: ' + stdout);
    console.log('stderr: ' + stderr);
    if (error !== null) {
      console.log('exec error: ' + error);
    }
});

3.2 参数说明

execFile 命令有四个参数,file和callbakc为必传参数,options、args为可选参数:

  • file 要执行程度的文件或命令名。字符串类型
  • args 要执行程度或命令的参数列表。数组类型
  • options 可选参数对象,与execoptions对象相同
  • callback 子进程执行完毕的回调函数。与execcallback函数相同
  • 返回值: ChildProcess 对象

3.3 用execFile方法执行命令

execFile方法也可以用于执行shell命令。与exec方法不同的是,execFile执行命令需要将要执行的命令和参数分别传入。示例如下:

var child_process = require('child_process');

child_process.execFile('ls', ['-l', '/tmp'], function (error, stdout, stderr) {
    console.log('执行结果: ' + stdout);
});


4. 运行Node模块:child_process.fork(modulePath[, args][, options])

fork方法用于运行Node模块,fork启动模块后会在父进程与子进程直接建立一个IPC管道,用于父子进程之间的通信。

4.1 使用示例

child.js是一个Node.js的模块,用fork方法运行这个模块。

var child_process = require('child_process');

var child = child_process.fork('./child.js');
child.on('message', function(m) {
  console.log('收到了父进程的消息:', m);
});

//发送消息到子进程
child.send({ hello: 'world' });

child.js代码如下:

process.on('message', function(m) {
  console.log('子进程收到了消息:', m);
});

//向父进程发送消息
process.send({ foo: 'bar' });


5. 进程控制与中止:child.kill([signal])

信号是进程间通信的一种简单方式,父进程可以通过信号对子进程进行控制。一些信号可以由子进程处理,而另一些信号只能由操作系统处理。如果进程收到一个不能处理的信号,进行就会终止。可以通过kill方法向子进程发送一个信号,实现对子进程的控制。kill方法默认发送的是SIGTERM(进程中止)信号。通过kill方法发送不同的signal可以实现对子进程的控制,而子进程可以捕获一些可处理的信号,进行响应及处理。

var child_process = require('child_process');
var child = child_process.spawn('tail', ['-f', '/var/log/system.log']);

//打印子进程的输出数据
child.stdout.on('data', function (data) {
  console.log('stdout: ' + data);
});

//5秒后,进程中止
setTimeout(function(){
	child.kill();
}, 5000);