Node.js中execFile,spawn,exec和fork简介

  Node.js子流程child_process模块提供四种不同方法执行外部应用:

1. execFile

2. spawn

3. exec

4. fork

所有这些都是异步,调用这些方法会返回一个对象,这对象是ChildProcess类的实例。

Node.js子流程

1. execFile

用于执行一个外部应用,应用退出后会返回一些可选蚕食和带有缓冲输出的callback。

child_process.execFile(file[, args][, options][, callback])

当外部应用存在时,Node程序将携带参数"-version"被执行,当外部应用退出时,回调函数被调用,回调函数带有子流程的标准输入输出,来自外部应用的标准输出将被内部缓冲保存。

运行下面代码将打印当前node版本:

const  execFile = require( 'child_process').execFile;
const  child = execFile( 'node', [ '--version'], ( error,  stdout,  stderr) => {
    if ( error) {
        console.error( 'stderr',  stderr);
        throw  error;
    }
    console.log( 'stdout',  stdout);
});

node是如何发现外部应用?它是由PATH环境变量,其中会指定一系列目录,可执行的外部应用驻留在这些目录中,如果外部应该被发现存在,无需该外部应用的绝对路径或相对路径就会被定位。

execFile是当需要执行外部应用并获得输出时使用,我们能使用它运行一个图片处理应用将图片从PNG转为JPG格式,我们只关心其成功与否,当外部应用产生大量数据以及我们需要实时使用这些数据时,execFile就不要使用。

2.spawn

 spawn 方法会在新的流程执行外部应用,返回I/O的一个流接口。

child_process.spawn(command[, args][, options])

使用案例:

const  spawn = require( 'child_process').spawn;
const  fs = require( 'fs');
function  resize( req,  resp) {
    const  args = [
        "-",  // use stdin 
        "-resize",  "640x",  // resize width to 640 
        "-resize",  "x360<",  // resize height if it's smaller than 360 
        "-gravity",  "center",  // sets the offset to the center 
        "-crop",  "640x360+0+0",  // crop 
        "-"  // output to stdout 
    ];
    const  streamIn = fs.createReadStream( './path/to/an/image');
    const  proc = spawn( 'convert',  args);
    streamIn.pipe( proc.stdin);
    proc.stdout.pipe( resp);
}

上面是一个express.js控制器函数中代码,我们使用流从一个图片文件读取,然后使用spawn方法生成convert程序,我们使用图片流喂给ChildProcess proc,只要这个proc对象产生数据,我们写入数据到一个可写的流中,用户无需等待整个图片转换完毕就能立即看见图片。

spawn返回一个对象流,对于输出大量数据然后需要读取的应用适合,因为是基于流,所有流好处有:

  • 低内存损耗
  • 自动处理后压back-pressure
  • 在缓冲块中懒生产或消费数据
  • 基于事件且非堵塞
  • 缓冲可以让你超过V8 heap内存限制

 

3.exec

这个方法将会生成一个子shell,能够在shell中执行命令,并缓冲产生的数据,当子流程完成后回调函数将会被调用,可带有:

  • 当命令成功执行,缓冲的数据
  • 当命令失败,错误信息
child_process.exec(command[, options][, callback])

与execFile 和 spawn相比,exec并没有参数,因为exec允许我们在shell中执行多个命令,当使用exec时,我们如果需要传输参数到命令行,它们应该作为整个命令字符串的一部分。

下面代码将会输出当前目录下所有递归条目:

const  exec = require( 'child_process').exec;
exec( 'for i in $( ls -LR ); do echo item: $i; done', ( e,  stdout,  stderr)=> {
    if ( e  instanceof Error) {
        console.error( e);
        throw  e;
    }
    console.log( 'stdout ',  stdout);
    console.log( 'stderr ',  stderr);
});

当在一个shell中运行命令,我们能实现在shell中所有功能,比如管道 重定向:

const  exec = require( 'child_process').exec;
exec( 'netstat -aon | find "9000"', ( e,  stdout,  stderr)=> {
    if ( e  instanceof Error) {
        console.error( e);
        throw  e;
    }
    console.log( 'stdout ',  stdout);
    console.log( 'stderr ',  stderr);
});

上面案例中,node会生成一个子shell,并执行命令: netstat -aon | find “9000”

exec只有需要利用shell功能时才能使用。

4.fork

child_process.fork()方法是child_process.spawn()特殊情况,子流程返回一个ChildProcess对象,这个ChildProcess会有附加通讯通道,允许消息在父子之间来回穿梭。fork方法会打开一个IPC通道,允许Node流程之间传递消息:

  • 如在子流程, process.on(‘message’) 和 process.send(‘message to parent’) 能被用来接受和发送数据
  • 入在父流程, 使用child.on(‘message’) 和 child.send(‘message to child’) 

每个流程都有自己的内存,都有它们自己的V8实例,启动至少30毫秒,每个大小是10mb

//parent.js 
const  cp = require( 'child_process');
const  n = cp.fork( `${ __dirname }/sub.js`);
n.on( 'message', ( m) => {
  console.log( 'PARENT got message:',  m);
});
n.send({ hello:  'world' });
//sub.js 
process.on( 'message', ( m) => {
  console.log( 'CHILD got message:',  m);
});
process.send({ foo:  'bar' });

因为Node主流程是单线程的,长期运行任务如计算等会堵塞主流程,因此,进来的请求不能被处理,应用变得不可响应,将这些长期运行任务放入主流程之外运行,fork一个新的node流程专门处理,这样主流程能够进行处理进来的请求保持可响应性。

 

Node.JS专题