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进行进程的管理,具体可以看下图。