本文深入解析Node.js中child_process模块的核心机制,详细阐述ChildProcess对象的事件、方法及属性。通过对比exec、execFile、spawn和fork四种子进程创建方式,结合具体代码示例,说明各自适用场景、参数配置及通信机制,帮助开发者高效利用多核资源进行进程管理。
一、理论
在研读多进程章节时,我意识到这部分内容较为繁杂,此前编写Process模块时也有所涉及。经过下午的深入梳理,发现其核心逻辑并不复杂,关键在于理清各模块间的关系。
鉴于现代计算机普遍采用多处理器架构,而Node.js默认单线程运行模式可能无法充分利用硬件资源,因此引入了child_process模块。该模块允许在父进程之外创建、派生并执行子进程,从而有效利用多核计算能力。
child_process模块提供了ChildProcess类,作为父进程访问子进程的接口。值得注意的是,Process模块本身也是ChildProcess对象:在父进程中访问process时,它代表父ChildProcess;而在子进程中访问Process时,则代表子ChildProcess对象。
理解一个对象通常需关注其事件、方法和属性,ChildProcess也不例外。下文将列举其常用的事件、方法及属性,以便快速掌握其核心功能。
事件:
message: 当ChildProcess对象调用send()方法发送数据时触发此事件。
error: 当工作进程内部发生错误时触发,处理程序接收一个错误对象作为唯一参数。
exit: 当工作进程结束时触发,处理程序接收两个参数:code(退出码)和signal(终止信号)。
close: 当工作进程的所有stdio流均终止时触发。与exit不同,由于多个进程可能共享相同的stdio流,close事件的触发时机和条件有所区别。
disconnect: 当在工作进程上调用disconnect()方法时触发。
方法:
kill([signal]): 向操作系统发送kill信号给子进程,默认信号为SIGTERM。
send(message,[sendHandle]): 向句柄发送消息,消息可以是字符串或对象。若提供sendhandle,可将TCP Server或socket对象发送给客户端,允许客户端进程共享相同的端口和地址。
disconnect(): 关闭父进程与子进程之间的进程通信(IPC)通道,并将父进程和子进程的连接标志均设置为false。
属性:
stdin: 可写流(Writable stream),用于向子进程输入数据。
stdout: 可读流(Readable stream),用于接收子进程的标准输出。
strerr: 用于输出错误的标准输出可读流(Readable stream)。注:原文此处拼写为strerr,通常应为stderr,但此处保留原文表述或依上下文理解为错误输出流。
pid: 子进程的ID。
connected: 布尔值。调用disconnect()后,该值设为false;当为false时,无法向子进程发送消息。
二、实践
1.exec()在另一进程执行一个系统命令
exec()函数在子shell中执行系统命令。它几乎能执行控制台提示符下支持的所有操作,包括二进制可执行文件、shell脚本、Python脚本或批处理文件。
exec(command,[options],callback)函数返回一个ChildProcess对象。
command: 字符串类型,指定在子shell中执行的命令。
options: 对象类型,指定执行命令时的配置参数,具体选项如下:
- cwd: 指定子进程执行的当前工作目录。
- env: 对象类型,指定property:value形式的键/值对,用于配置子进程的环境变量。
- encoding: 指定存储命令输出时,输出缓冲区使用的编码格式。
- maxBuffer: 指定stdout和stderr输出缓冲区的大小,默认值为200*1024字节。
- timeout: 指定父进程在终止子进程前,若子进程未完成需等待的毫秒数,默认值为0(无超时限制)。
- killSignal: 指定终止子进程时使用的kill信号,默认值为SIGTERM。
callback: 回调函数,接收error、stdout、stderr三个参数。
var childProcess = require('child_process');
var options = {maxBuffer:100*1024, encoding:'utf8', timeout:5000};
var child = childProcess.exec('dir /B', options,
function (error, stdout, stderr) {
if (error) {
console.log(error.stack);
console.log('Error Code: '+error.code);
console.log('Error Signal: '+error.signal);
}
console.log('Results: \n' + stdout);
if (stderr.length){
console.log('Errors: ' + stderr);
}
});
child.on('exit', function (code) {
console.log('Completed with code: '+code);
});输出结果:
"C:\Program Files (x86)\JetBrains\WebStorm 11.0.3\bin\runnerw.exe" F:\nodejs\node.exe child_process_exec.js
Completed with code: 0
Results:
chef.js
child_fork.js
child_process_exec.js
child_process_exec_file.js
child_process_spawn.js
cluster_client.js
cluster_server.js
cluster_worker.js
file.txt
process_info.js
Process finished with exit code 02.execFile()在另一个进程上执行一个可执行文件
execFile()与exec()类似,主要区别在于execFile()不使用子shell,因此执行的命令必须是二进制可执行文件。Linux的shell脚本和Windows的批处理文件不能使用execFile()。
execFile(file,args,options,callback)同样返回一个ChildProcess对象。
file: 字符串类型,指定要执行的可执行文件路径。
args: 数组类型,指定传递给可执行文件的命令行参数。
options: 配置选项,参考exec()的参数说明。
callback: 回调函数,参考exec()的回调参数说明。
var childProcess = require('child_process');
var options = {maxBuffer:100*1024, encoding:'UTF-16', timeout:5000};
var child = childProcess.execFile('ping.exe', ['-n', '1', 'baidu.com'],
options, function (error, stdout, stderr) {
if (error) {
console.log(error.stack);
console.log('Error Code: '+error.code);
console.log('Error Signal: '+error.signal);
}
console.log('Results: \n' + stdout.toString());
if (stderr.length){
console.log('Errors: ' + stderr.toString());
}
});
child.on('exit', function (code) {
console.log('Child completed with code: '+code);
});3.spawn()在另一个Node.js实例中产生一个进程
spawn(cmd,[args],[options])函数产生一个进程,连接它们之间的stdio、stdout、stderr的管道,然后在新的线程中使用spawn()执行文件。它和上面两个的主要区别是产生的进程中的stdin可以进行配置,并且stdout、stderr都是父进程中的Readable流。这意味着exec()、execFile()必须先执行完成,才能读取缓冲区输出,但一旦一个spawn()进程的输出数据已被写入就可以读取它(这个可以从它们3个的输出结果的顺序可以看出,上面两个都是先执行exit事件,而spawn()exit处理是在后面)。
cmd、args和options与前述两个函数类似。options还可设置cwd、env,以及detached和stdio。
detached: 布尔值,设为true时使子进程成为新进程组的组长,即使父进程退出也会继续运行。可使用child_unref()使得父进程退出前不等待子进程。
stdio: 定义子进程stdio配置([stdin,stdout,stderr]),默认值为[0,1,2]。此字符串定义每个输入输出流的配置。
var spawn = require('child_process').spawn;
var options = {
env: {user:'brad'},
detached:false,
stdio: ['pipe','pipe','pipe']
};
var child = spawn('netstat', ['-e']);
child.stdout.on('data', function(data) {
console.log(data.toString());
});
child.stderr.on('data', function(data) {
console.log(data.toString());
});
child.on('exit', function(code) {
console.log('Child exited with code', code);
});4.实现子派生
Node.js提供了另一种进程产生方式——派生(fork)。它主要在单独处理器上运行另一个V8引擎实例中的Node.js模块代码,可用于并行运行多个服务。由于启动新V8实例需要时间且每个实例约占用10M内存,因此建议将派生进程设计为存活期较长、无需大量派生的场景。与spawn()不同,fork不能为子进程配置stdio,但可通过ChildProcess对象中的send()机制在父进程与子进程间通信。
fork(modulePath,[args],[options])对象同样返回一个ChildProcess对象。
modulePath: 字符串类型,指定由新Node.js实例启动的JavaScript文件路径。
args: 数组类型,指定传递给node命令的命令行参数。
options: 参数对象,指定执行命令时的配置设置。
cwd、env: 配置项,说明同上。
encoding: 指定数据写入输出流及穿越send() IPC机制时使用的编码。
execPath: 指定用于创建产生Node.js进程的可执行文件路径。
silent: 布尔值,设为true时,派生进程中的stdout和stderr不与父进程关联,默认值为false。
Child.send(): 父进程向子进程发送消息;Process.send(): 子进程向父进程发送消息。
var child_process = require('child_process');
var options = {
env:{user:'Brad'},
encoding:'utf8'
};
function makeChild(){
var child = child_process.fork('chef.js', [], options);
child.on('message', function(message) {
console.log('Served: ' + message);
});
return child;
}
function sendCommand(child, command){
console.log("Requesting: " + command);
child.send({cmd:command});
}
var child1 = makeChild();
var child2 = makeChild();
var child3 = makeChild();
sendCommand(child1, "makeBreakfast");
sendCommand(child2, "makeLunch");
sendCommand(child3, "makeDinner");process.on('message', function(message, parent) {
var meal = {};
switch (message.cmd){
case 'makeBreakfast':
meal = ["ham", "eggs", "toast"];
break;
case 'makeLunch':
meal = ["burger", "fries", "shake"];
break;
case 'makeDinner':
meal = ["soup", "salad", "steak"];
break;
}
process.send(meal);
});输出结果:
"C:\Program Files (x86)\JetBrains\WebStorm 11.0.3\bin\runnerw.exe" F:\nodejs\node.exe child_fork.js
Requesting: makeBreakfast
Requesting: makeLunch
Requesting: makeDinner
Served: ham,eggs,toast
Served: burger,fries,shake
Served: soup,salad,steak上述代码在主进程中创建了3个子进程,父进程向子进程发送消息,子进程接收后并向父进程回复消息。
到此这篇关于Node.js进程管理之子进程的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持本站。