Node.js可以高效的处理I/O操作,如果处理CPU密集型的任务可能会阻塞事件循环。因此,Node.js允许创建进程,将CPU密集型任务分配给另一个进程处理,释放事件循环。在Node.js中,子进程和父进程能够进行双向通信,并且一定程度上,父进程可以监控子进程。
另一种使用子进程的情况是执行一个外部命令,并在执行结束后,让Node.js获得返回的结果。
执行外部命令
当需要执行一个外部shell命令或者可执行文件时,可以使用child_process模块:
1 | var child_process = require('child_process'); |
exec的第一参数是一个字符串,表示准备执行的shell命令,第二个参数是一个回调函数,在命令结束或发生错误时调用,回调函数应有三个参数: error、stdout和stderr。例:
1 | exec('ls', function(error, stdout, stderr){ |
如果出现错误,第一个参数是Error的实例,如果第一个参数不包含错误,第二个参数stdout将会包含命令的输出信息,最后一个参数包含命令的错误输出信息。
生成子进程
child_process.exec()函数虽然能启动进程,但也有一些缺点:
- 除了命令行参数和环境变量之外,exec()函数不允许与子进程通信;
- 子进程的输出是被缓存的,结果是无法对其进行流操作,可能会耗尽内存。
Node.js的 child_process 模块允许对子进程的启动、终止以及与其进行交互进行精细控制。可以在程序(父进程)中新建一个进程(子进程),一旦启动了新的子进程,Node.js就创建了一个双向通信通道,两个进程可以利用这条通道互相收发字符串形式的数据,父进程可以对子进程施加一些控制、向其发送信号或者强制终止子进程。
创建子进程
基于child_process.spawn函数创建子进程:
1 | var spawn = require('child_process').spawn; |
上面代码创建了一个子进程,通过tail命令监视文件,并且输出的数据被附加到stdout流中,spawn函数会返回一个ChildProcess对象,该对象是一个句柄,封装了实际进程的访问。
监听子进程的输出数据
任何一个子进程句柄都有一个属性stdout,它以流的形式表示子进程的标准输出信息,然后可以在这个流上绑定事件。
1 | //在我的js目录下有个同级的info.txt文件,执行下面程序,会输出info.txt的内容 |
除了标准输出之外,进程还有一个默认输出流:standard error流,进程通常利用该流输出错误信息。
1 | child.stderr.on('data', function(data){ |
向子进程发送数据
除了从子进程的输出流中获取数据之外,父进程也向子进程的标准输入流中写入数据,相当于向子进程发送数据,标准输入流是用ChildProcess.stdin属性来表示的。
子进程也可以使用process.stdin流来监听数据。但是,首先要恢复流,因为在默认情况下,它处于暂停状态。
写一个plus.js文件如下:
1 | process.stdin.resume(); |
直接调用
1 | node plus.js |
运行后,程序等待输入,如果输入一个整数按回车键,屏幕上就会显示一个加1后的返回的整数。Ctrl+c 退出应用程序。
创建一Node进程来使用plus.js提供计算服务。
1 | var spawn = require('child_process').spawn; |
当子进程退出时获得通知
当子进程退出时,会在父进程上触发一个事件。
1 | var spawn = require('child_process').spawn; |
监听子进程exit事件,当该事件出现,就将子进程终止的相关信息打印至控制台。子进程的退出码会被传递给回调函数,作为其第一个参数。一些程序以非0的退出码表示发生错误。
1 | var spawn = require('child_process').spawn; |
向进程发送信号并终止进程
一般而言,可用使用child.kill方法向子进程发送一个信号,默认发送的是SIGTERM信号。
1 | var spawn = require('child_process').spawn; |
还可以将一个标识信号类型的字符串传递给child.kill方法,作为其唯一参数。
1 | child.kill('SIGUSR2'); |
注意,尽管方法名是kill,但是发送的信号却不一定终止进程。在Node中,子进程可以定义一个信号处理程序来重写信号。
1 | process.on('SIGUSR2', function(){ |
上述代码,为SIGUSR2定义了一个信号处理程序,进程在收到该信号后不会被终止,而是输出了’Got a SIGUSR2 signal’ 。
SIGKILL和SIGSTOP是由操作系统处理的特殊信号,并且进程不能重写其默认行为,即使已经定义了处理程序,它们也会终止进程。