Node.js的进程管理的深入理解(2)
在execFile中,最终调用的是spawn
方法。
exports.execFile = function execFile(file /* , args, options, callback */) { let args = []; let callback; let options; var child = spawn(file, args, { // ... some options }); return child; }
exec会将spawn的输入输出流转换成String,默认使用UTF-8的编码,然后传递给回调函数,使用回调方式在node中较为熟悉,比流更容易操作,所以我们能使用exec方法执行一些shell
命令,然后在回调中获取返回值。有点需要注意,这里的buffer是有最大缓存区的,如果超出会直接被kill掉,可用通过maxBuffer属性进行配置(默认: 200*1024)。
const { exec } = require('child_process'); exec('ls -lh /home', (error, stdout, stderr) => { console.log(`stdout: ${stdout}`); console.log(`stderr: ${stderr}`); });
fork
fork最后也是调用spawn来创建子进程,但是fork是spawn的一种特殊情况,用于衍生新的 Node.js 进程,会产生一个新的V8实例,所以执行fork方法时需要指定一个js文件。
exports.fork = function fork(modulePath /* , args, options */) { // ... options.shell = false; return spawn(options.execPath, args, options); };
通过fork创建子进程之后,父子进程直接会创建一个IPC(进程间通信)通道,方便父子进程直接通信,在js层使用 process.send(message)
和 process.on('message', msg => {})
进行通信。而在底层,实现进程间通信的方式有很多,Node的进程间通信基于libuv实现,不同操作系统实现方式不一致。在*unix系统中采用Unix Domain Socket方式实现,Windows中使用命名管道的方式实现。
常见进程间通信方式:消息队列、共享内存、pipe、信号量、套接字
下面是一个父子进程通信的实例。
parent.js
const path = require('path') const { fork } = require('child_process') const child = fork(path.join(__dirname, 'child.js')) child.on('message', msg => { console.log('message from child', msg) }); child.send('hello child, I\'m master')
child.js
process.on('message', msg => { console.log('message from master:', msg) }); let counter = 0 setInterval(() => { process.send({ child: true, counter: counter++ }) }, 1000);
小结
其实可以看到,这些方法都是对spawn方法的复用,然后spawn方法底层调用了libuv进行进程的管理,具体可以看下图。