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