Node多进程
Node 多进程开发入门
Node多进程核心是创建一个子进程,子进程依附在当前Node进程下面
核心类是 child_process
文档路径:http://nodejs.cn/api/child_process.html
子进程概念:是系统进行资源分配和调度的基本单位,是操作系统结构的基础
进程的概念只要有两点
- 进程是一个实体。每一个进程都有它的地址空间
- 进程是一个“执行中的程序”,存在嵌套关系
进程开线程,进程和线程的区别?
查看进程(os)
ps -ef 查看所有进程
Last login: Sun Feb  7 15:42:44 on ttys002
jolly@JollydeMacBook-Pro jolly-cli-dev % ps -ef
  UID   PID  PPID   C STIME   TTY           TIME CMD
    0     1     0   0 301220  ??        14:35.92 /sbin/launchd
    0   112     1   0 301220  ??         0:56.66 /usr/sbin/syslogd
    0   113     1   0 301220  ??         2:26.60 /usr/libexec/UserEventAgent (System)
    0   116     1   0 301220  ??         0:13.29 /System/Library/PrivateFrameworks/Uninstall.framework/Resources/uninstalld
    0   117     1   0 301220  ??         0:54.79 /usr/libexec/kextd
    0   118     1   0 301220  ??         5:35.10 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/Support/fseventsd
- UID 获得权限的用户的UID
- PID 进程的ID- ps -ef|grep PID可帅选出对应进程
 
- PPID 父进程的ID,体现进程的嵌套关系
vscode 启动 node 调试时的父子进程关系图。体现了进程嵌套

5-2 child_process异步方法使用教程(exec&execFile)
const cp = require('child_process');
child_process用法:
异步用法
- exec
- execFile
- fork
- spawn
同步用法
- execSync
- execFileSync
- spawnSync
异步
- exec执行- shell命令- cp.exec('ls -la', function(err, stdout, stderr){
 console.log('err: ', err); // 错误
 console.log('stdout: ', stdout); // 正常运行输出的结果,后面会有个换行
 console.log('stderr: ', stderr); // 异常输出的结果
 });- jolly@JollydeMacBook-Pro child_process % node index.js
 err: null
 stdout: total 8
 drwxr-xr-x 3 jolly staff 96 2 7 16:58 .
 drwxr-xr-x 14 jolly staff 448 2 7 16:58 ..
 -rw-r--r-- 1 jolly staff 189 2 7 16:59 index.js
 stderr:- 第二个参数是一个 option 对象- cwd设置执行的目录
- timeout 设置执行命令超时的时间,默认是0(不超时)
 
 
- 第二个参数是一个 option 对象
- execFile执行- shell文件- 有四个参数
 - 如果第一个参数不是路径,而是命令,那么通过 - which 命令找到对应的文件- cp.execFile('ls', ['-la'], function(err, stdout, stderr){
 console.log('err: ', err);
 console.log('stdout: ', stdout);
 console.log('stderr: ', stderr);
 });- 和上面 - exec执行效果一样- execFile第二个参数是,传入文件的参数
- 和 - exec的区别- exec支持更复杂的- shell命令:- ls -la|grep node_modules
- 但 - execFile执行- [-la|grep node_modules]会报错,不能执行成功。因为- grep不是- execFile执行文件- ls的参数
- 可以执行 - .shell文件:将- ls -al|grep node_modules写在- .shell文件中- chmod +x test.shell添加执行权限
 - test.shell文件当中的内容- ls -la|grep node_modules
 echo $1- $1代表- execFile传入的第一个参数
 
- exec也能执行指定文件,但是不支持传入参数
 
 
- spwan执行- shell文件- 如果第一个参数不是路径,而是命令,那么通过 - which 命令找到对应的文件- const child = cp.spawn(path.resolve(__dirname, 'test.shell'), ['-la'], {
 cwd: path.resolve('.')
 })
 child.stdout.on('data', function(chunk) {
 console.log('stdout: ', chunk.toString()); //一次输出一个字符串
 });
 child.stderr.on('data', function(chunk) {
 console.log('stderr: ', chunk.toString());
 });
 child.on('error', e => { // 监听错误
 process.exit(1);
 });
 child.on('exit', e => {// 监听执行成功后的退出事件
 });- option参数 - stdio选项用于配置在父进程和子进程之间建立的管道。值:- pipe默认值,在子进程和父进程之间创建一个管道。
- ignore静默执行,不会收到反馈
- inherit将相应的 stdio 流传给父进程或从父进程传入。将输入、输出、错误,绑定到父进程的- process.stdin、- process.stdout和- process.stderr上。直接能看到打印,还带动画(进度)信息 。
 
 
- fork使用- node执行命令- 一个参数:模块路径
- 和 require()的区别- require加载的- js模块是在主进程中执行的。- fork测试在子进程中的执行的,执行的- js文件- process.pid会发生变化。
 
 - const child = cp.fork(path.resolve(__dirname, 'child.js'));- 适合执行耗时任务。
 
同步
执行简单 shell 命令
- execSync执行- shell命令- const stdout = cp.execSync('ls -la|grep node_modules');
 console.log(stdout.toString()); // stdout 是个 buffer
- execFileSync执行- shell文件- const stdout = cp.execFileSync('ls', ['-la']);
 console.log(stdout.toString()); // stdout 是个 buffer
- spawnSync执行- shell文件- const ret = cp.spawnSync('ls', ['-la']);
 console.log(ret.stdout.toString()); // ret 是个 buffer
- exec、- execFile、- fork底层都是调用- spawn的
- 何时使用 - spawn、- exec、- execFile- spawn适合耗时任务(比如:- npm install),需要不断日志。- spawn逐条执行命令
- exec/- execFile:开销比较小的任务。整个执行完后返回
 
- 使用 - path.resovle('./','shell.js')时会报错,因为node的执行上下文,不一定是当前目录。要使用- __dirname,比如- path.resovle(__dirname,'shell.js')
5-4 child_process fork用法及父子进程通信机制讲解
- fork主要是使用node来执行我们的命令。
- fork会执行两个进程 主进程与子进程。
- fork的本质也是调用spawn。
const child = cp.fork(path.resolve(__dirname, 'child.js'));
// 向子进程发送消息
child.send('hello child process', () => {
  // child.disconnect(); // 断开主、子进程直接的连接,否则,命令行将进入等待状态
});
// 接受子进程消息
child.on('mssage', msg => {
  console.log('msg: ', msg);
  child.disconnect();
});
console.log('main pid', process.pid);
child.js
console.log('child process');
console.log('child pid', process.pid);
// 接受主进程消息
process.on('message', msg => {
  console.log('msg: ', msg);
});
// 向主进程发送消息
process.send('hello main process');
执行结果
jolly@JollydeMacBook-Pro child_process % node index.js
main pid 61868
child process
child pid 61869
msg:  hello child process
msg:  hello main process
注意:子进程向主进程发送消息,容易造成死循环
windows 子进程执行 node 命令
cp.spawn('cmd', ['/c', 'node', '-e', code]); // '/c' 表示静默执行
child_process异步源码解析
源码解读解决的问题
- exec和- execFile到底有什么区别
- 为什么 exec/execFile/fork都是通过spawn实现的,spawn的作用到底是什么?
- 为什么 spawn调用后没有回调,而exec/execFile能够回调?
- 为什么 spawn调用后需要手动调用child.stdout.on('data', callback),这里的child.stdio/child.stderr到底是什么?
- 为什么有 data/error/exit/close这么多种回调,它们的执行顺序到底是什么怎样的?
exec源码解读

- exec和- execFile的区别就是参数的区别
- 在 - execFile中调用- spawn并且监听了- stderr和- stdout的- data事件,执行事件处理函数,- exec和- execFile的回调函数就是这个事件处理函数。所以- exec和- execFile有回调函数
- 执行 - exec时,最后调用- spawn规范后的参数 - 出现了 /bin/sh
- envPairs是环境变量
 
- 出现了 
- this._handle是实际的进程
- 执行 - child.spawn实际执行的是- this._handle.spawn,执行后开启新进程
- exec执行后也是可以得到子进程对象的
- 课程中调试源码时, - ls -la|grep node_modules报错,而直接在- bash中运行不报错,是因为- node有执行的环境,执行的环境不一样(执行时,所在路径不一样)- 统一执行环境后(没有node_modules文件夹),bash中不会报错,但是没有结果。但调试还报错,是因为bash处理了错误。我们代码中输出了错误而已
 
- 统一执行环境后(没有node_modules文件夹),
shell 的使用
- 方法一:直接执行shell文件 - /bin/sh test.shell
- 方法二:直接执行 - shell语句- /bin/sh -c "ls -la"- 所以,没有 - -c要指定文件路径
- shell命令- ls -la===- /bin/sh -c "ls -la"
 
exec 源码精读
- 对象的扩展运算符进行浅拷贝 - // 等同于 {...Object(true)}
 {...true} // {}
 // 等同于 {...Object(undefined)}
 {...undefined} // {}
 // 等同于 {...Object(null)}
 {...null} // {}
 {...'hello'}
 // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
 { ...['a', 'b', 'c'] };
 // {0: "a", 1: "b", 2: "c"}- 浅拷贝和深拷贝 - 浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
- 深拷贝:将一个对象从内存中完整的拷贝一份出来,包括属性指向的引用类型,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
 
- 注意:第二个参数传任何非 - function类型,都会产生获得一个对象。- function normalizeExecArgs(command, options, callback) {
 if (typeof options === 'function') {
 callback = options;
 options = undefined;
 }
 // 浅拷贝
 options = { ...options }; // 将任意非 function 都将转化为参数
 options.shell = typeof options.shell === 'string' ? options.shell : true; // 得到shell属性。
 return {
 file: command,
 options: options, // options 至少有一个属性:shell
 callback: callback
 };
 }
 
- option.shell可以是一个字符串,用来执行命令的文件。默认值: Unix 上是- '/bin/sh',Windows 上是- process.env.ComSpec
- execFile中首先对参数逐个判断,判断逻辑有点意思- function execFile(file /* , args, options, callback */) {
 let args = [];
 let callback;
 let options;
 // 解析可选参数(第一个参数是 shell 文件路径),使用argument
 let pos = 1;
 if (pos < arguments.length && Array.isArray(arguments[pos])) { // 获得传入shell文件的参数
 args = arguments[pos++];
 } else if (pos < arguments.length && arguments[pos] == null) { // 第二个参数给 null 跳过第二个参数解析
 pos++;
 }
 if (pos < arguments.length && typeof arguments[pos] === 'object') { // 参数是 Object 类型,认为是options
 options = arguments[pos++];
 } else if (pos < arguments.length && arguments[pos] == null) { // 参数值是 null,跳过
 pos++;
 }
 if (pos < arguments.length && typeof arguments[pos] === 'function') { // 获得回调函数
 callback = arguments[pos++];
 }
 if (!callback && pos < arguments.length && arguments[pos] != null) { // 经过以上步骤,传参了但没有解析到回调函数,报错。
 throw new ERR_INVALID_ARG_VALUE('args', arguments[pos]);
 }
 ...
 }- 这样的参数解析,可以不用固定参数的顺序 
- 查看 - ERR_INVALID_ARG_VALUE的报错- const cp = require('child_process');
 cp.exec('ls -la', null,'sdds');- 要传入第二个参数时,才能看见第三个参数的报错。原因见”对象的扩展运算符“ - olly@192 child_process % node index.js
 child_process.js:202
 throw new ERR_INVALID_ARG_VALUE('args', arguments[pos]);
 ^
 TypeError [ERR_INVALID_ARG_VALUE]: The argument 'args' is invalid. Received 'sdds'
 at Object.execFile (child_process.js:202:11)
 at Object.exec (child_process.js:145:25)
 at Object.<anonymous> (/Users/jolly/Desktop/imooc/child_process/index.js:4:4)
 at Module._compile (internal/modules/cjs/loader.js:959:30)
 at Object.Module._extensions..js (internal/modules/cjs/loader.js:995:10)
 at Module.load (internal/modules/cjs/loader.js:815:32)
 at Function.Module._load (internal/modules/cjs/loader.js:727:14)
 at Function.Module.runMain (internal/modules/cjs/loader.js:1047:10)
 at internal/main/run_main_module.js:17:11 {
 code: 'ERR_INVALID_ARG_VALUE'
 }
 
 
- 数组的浅拷贝 - //args = args.slice(0)
 var a = [1, 2, 3];
 var b = a.slice(0); // b: [1, 2, 3]
 a === b; // false
- spawn中的命令拼接部分- if (options.shell) {
 const command = [file].concat(args).join(' '); // 拼接命令文件和传入的参数
 // Set the shell, switches, and commands.
 if (process.platform === 'win32') { // windows
 if (typeof options.shell === 'string') // 自定义执行shell的文件
 file = options.shell;
 else
 file = process.env.comspec || 'cmd.exe';
 // '/d /s /c' is used only for cmd.exe.
 if (/^(?:.*\\)?cmd(?:\.exe)?$/i.test(file)) { // 匹配任意路径下的 cmd.exe。这里指定了 cmd.exe 的路径
 args = ['/d', '/s', '/c', `"${command}"`]; // '/d /s /c' 仅用于 cmd.exe.
 options.windowsVerbatimArguments = true; // options 中的 windowsVerbatimArguments 参数
 } else {
 args = ['-c', command];
 }
 } else {
 if (typeof options.shell === 'string')
 file = options.shell;
 else if (process.platform === 'android') // 安卓系统
 file = '/system/bin/sh';
 else
 file = '/bin/sh'; // 默认使用 '/bin/sh'
 args = ['-c', command];
 }
 }
- spawn中的- new ChildProcess()- EventEmitter.call(this);之后,可以分发事件了。- emit分发
- on监听
 
- this._handle.onexit进程执行完之后回调
- child.spawn/- ChildProcess.prototype.spawn- getValidStdio()创建输入输出错误流- 输入流,子进程只有读权限 
- 输出流,子进程只有写权限 
- new Pipe()创建- socket通信,调用- pipe_wrap
- ipc建立进程间的双向通信,在- fork时创建
 
- 循环建立父子进程 socket 通信 - socket 对象使用 on('data')监听
 
 
 
node_process回调调用流程

- Process 执行命令- child._handle.spawn(options)执行命令
- exitCode为0,表示执行成功,小于0表示失败
 
- 命令执行成功后,往”流“中写入信息,回调 onStreamRead方法读取流中信息
- onStreamRead每读取完一条流中信息,调用一次- onReadableStreamEnd
- maybeClose()中,判断所有- socket关闭后,关闭子进程
- 两条线:- 子进程的执行线
- 流的读取线
 
事件处理函数执行顺序
const child = cp.execFile('ls -la', function(err, stdout, stderr){
  console.log('callback start-----------');
  console.log('err: ', err);
  console.log('stdout: ', stdout);
  console.log('stderr: ', stderr);
  console.log('callback end-----------');
});
child.on('error', chunk => {
  console.log('error! ', chunk);
})
child.stdout.on('data', chunk => {
  console.log('stdout data: ', chunk);
});
child.stderr.on('data', chunk => {
  console.log('stderr data: ', chunk);
});
child.stdout.on('close', chunk => {
  console.log('stdout close');
});
child.stderr.on('close', chunk => {
  console.log('stderr close');
});
child.on('exit', (exitCode, signalCode) => {
  console.log('exit! ', exitCode, ' ', signalCode);
});
child.on('close', (exitCode, signalCode) => {
  console.log('close! ', exitCode, ' ', signalCode);
});
jolly@192 child_process % node index.js
stdout data:  total 24
drwxr-xr-x   6 jolly  staff   192  2 10 15:11 .
drwxr-xr-x  14 jolly  staff   448  2  7 16:58 ..
drwxr-xr-x   3 jolly  staff    96  2 10 15:11 .vscode
-rw-r--r--   1 jolly  staff   225  2  7 21:10 child.js
-rw-r--r--   1 jolly  staff  1901  2 11 16:50 index.js
-rwxr-xr-x   1 jolly  staff    15  2  7 20:19 test.shell
exit!  0   null
stderr close
callback start-----------
err:  null
stdout:  total 24
drwxr-xr-x   6 jolly  staff   192  2 10 15:11 .
drwxr-xr-x  14 jolly  staff   448  2  7 16:58 ..
drwxr-xr-x   3 jolly  staff    96  2 10 15:11 .vscode
-rw-r--r--   1 jolly  staff   225  2  7 21:10 child.js
-rw-r--r--   1 jolly  staff  1901  2 11 16:50 index.js
-rwxr-xr-x   1 jolly  staff    15  2  7 20:19 test.shell
stderr:  
callback end-----------
close!  0   null
stdout close

exec 执行和回调脑图

颜色说明:
- 黄色:回调执行过程
- 紫色:广播事件
- 绿色:进程 error流程
关于 stderr 
- 当命令执行失败,如 lss -ls时- ChildProcess.prototype.spawn()中- exitCode是 0,并不小于0
 
Buffer 对象的字符串解码器
在 fork 流程,setupChannel(child, ipc) 设置,其中涉及 Buffer 对象的字符串解码。
string_decoder 模块提供了一个 API,用一种能保护已编码的多字节 UTF-8 和 UTF-16 字符的方式将 Buffer 对象解码为字符串。基本用法
const { StringDecoder } = require('string_decoder');
const decoder = new StringDecoder('utf8');
const cent = Buffer.from([0xC2, 0xA2]);
console.log(decoder.write(cent));
const euro = Buffer.from([0xE2, 0x82, 0xAC]);
console.log(decoder.write(euro));
fork 源码解读

- 剩余部分见 exec执行脑图
- stdio ipc通信:[0, 1, 2, 'ipc']
- process.execPath拿到- node路径
- 重点 getValidStdio(stdio, false)- 执行setupChannel(this, ipc),增强ipc功能,在父、子进程之间启动ipc:channel.readStart()- new Control(channel)创建- control对象,用于执行- ipc的- ref和- unref方法
- 有数据读取时,进入 channel.onread
 
 
- 执行
- child.send()调用- target.send()进行进程通信, 使用- pipe进行数据传递
- 在执行的 js文件中,process.send()也是使用target.send()进行通信
Node 多进程源码总结
- exec/execFile/spawn/fork的区别- exec: 原理是调用- bin/shell -c执行我们传入的- shell脚本,调用- execFile,但传参做了处理
- execFile:原理是直接执行我们传入的- file和- args,底层调用- spawn创建和执行子进程,但通过监听- spawn中广播的事件,建立了回调,且一次性将所有的- stdout和- stderr结果返回
- spawn:原理是调用- internal/child_process,实例化了- ChildProcess子进程对象,再调用- ChildProcess.prototype.spawn()创建子进程并执行命令,底层调用了- child._handle.spawn()执行- C++ process_wrap中的- spawn方法。执行过程是异步的。执行完后,通过 pipe 进行单向数据通信,通信结束后,子进程发起- child._handle.onexit回调,同时 socket 会执行- close回调。
- fork:原理是通过- spawn创建子进程和执行命令。使用- node执行命令,通过- setupchannel创建- IPC用于子进程和父进程之间的双向通信
 
- data/error/exit/close回调的区别- data:主进程读取数据过程中,通过- onStreamRead发起回调
- error:命令执行失败后发起的回调
- exit:子进程关闭完成后发起的回调
- close:子进程所有- Socket通信端口全部关闭后发起的回调
- stdout close/- stderr close:特定的 PIPE 读取完成后调用- onReadableStreamEnd()关闭- Socket时发起的回调。
 
child_process同步源码解析
execSync、 execFileSync 和 spawnSync 执行流程

execSync 、 execFileSync 和 spawnSync 的区别
- execSync和- execFileSync底层都是调用- spawnSync。但是- execSync和- execFileSync在调用- spawnSync之前的参数规范化逻辑不一样:- execSync和- exec都是调用- normalizeExecArgs()
- execFileSync、- spawn及- spawnSync调用的是- normalizeSpawnArguments()
 
- spawnSync返回的是- child_process.spawnSync(opts)中- spawn_sync.spawn(options)(- C++代码) 执行返回的结果- result
- execSync和- execFileSync都将- spawnSync的执行结果做了同样的处理- 有错误,直接从主进程 - threw错误- 在 - execFileSync('ls -la')找不到文件时- result.error 
- 错误日志 - child_process.js:642
 throw err;
 ^
 Error: spawnSync ls -la ENOENT
 at Object.spawnSync (internal/child_process.js:1041:20)
 at spawnSync (child_process.js:607:24)
 at Object.execFileSync (child_process.js:634:15)
 at Object.<anonymous> (/Users/jolly/Desktop/imooc/child_process/index.js:7:19)
 at Module._compile (internal/modules/cjs/loader.js:959:30)
 at Object.Module._extensions..js (internal/modules/cjs/loader.js:995:10)
 at Module.load (internal/modules/cjs/loader.js:815:32)
 at Function.Module._load (internal/modules/cjs/loader.js:727:14)
 at Function.Module.runMain (internal/modules/cjs/loader.js:1047:10)
 at internal/main/run_main_module.js:17:11 {
 errno: 'ENOENT',
 code: 'ENOENT',
 syscall: 'spawnSync ls -la',
 path: 'ls -la',
 spawnargs: [],
 error: [Circular],
 status: null,
 signal: null,
 output: null,
 pid: 6262,
 stdout: null,
 stderr: null
 }
- result.stdout和- result.stderr中是- null
 
- 在命令本身错误时: - lss -la- result.error = undefined
- result.stdout中的- ArrayBuffer为空
- result.stderr中- ArrayBuffer有值。- result.stderr中的信息,将通过- process.stderr.write(ret.stderr)打印在主进程的日志中:- /bin/sh: lss: command not found
- result.status !== 0,打印- result.stderr之后,threw 错误信息
 
 
- 没有错误,返回执行成功的结果输出流 - stdout
 
使用示例
const cp = require('child_process');
const path = require('path');
- execSync- const stdout = cp.execSync('lss -la');
 console.log('stdout: ', stdout.toString()); // 有异常不会执行
- execFileSync- const stdout = cp.execFileSync(path.resolve(__dirname, 'test.shell'));
 console.log('stdout: ', stdout.toString()); // 有异常不会执行
- spawnSync- const result = cp.spawnSync(path.resolve(__dirname, 'test.shell'));
 if (result.status === 0) { // 没有异常
 console.log(result.stdout.toString())
 } else {
 console.log(result)
 }
cluster 工作原理
单个 Node.js 实例运行在单个线程中。 为了充分利用多核系统,有时需要启用一组 Node.js 进程去处理负载任务。
  cluster 模块可以创建共享服务器端口的子进程。
  工作进程由 child_process.fork() 方法创建,因此它们可以使用 IPC 和父进程通信,从而使各进程交替处理连接服务。
require 加载内置模块解析
判断内置模块
源码
function NativeModule(id) {
  this.filename = `${id}.js`;
  this.id = id;
  this.exports = {};
  this.module = undefined;
  this.exportKeys = undefined;
  this.loaded = false;
  this.loading = false;
  this.canBeRequiredByUsers = !id.startsWith('internal/');
}
// ...
const {
  moduleIds,
  compileFunction
} = internalBinding('native_module');
NativeModule.map = new Map();
for (let i = 0; i < moduleIds.length; ++i) {
  const id = moduleIds[i];
  const mod = new NativeModule(id);
  NativeModule.map.set(id, mod);
}
- moduleIds:内置模块的路径+文件ming。通过- C++代码拿到
- this.canBeRequiredByUsers:是否是内置模块,- moduleIds中,- internal/文件夹下都是内置模块- cluster执行流程 
- cluter对象,是- EventEmitter对象的实例,具有- event- emit、- on等方法
- cluster.fork([env])- env- <object>要添加到进程环境变量的键值对。
- 返回:cluster.worker
 
- cluster.setupMaster([settings])- settings- <object>
- 用于修改默认的 fork行为。 一旦调用,将会按照settings对cluster.settings进行设置。
- 所有的设置只对后来的 .fork()调用有效,对之前的工作进程无影响。
 
- process.execArgv- 属性返回当·Node.js 进程被启动时,Node.js 特定的命令行选项。 这些选项在 - process.argv属性返回的数组中不会出现,并且这些选项中不会包括- Node.js的可执行脚本名称或者任何在脚本名称后面出现的选项。 这些选项在创建子进程时是有用的,因为他们包含了与父进程一样的执行环境信息。- $ node --harmony script.js --version- process.execArgv的结果:- ['--harmony']- process.argv的结果:- ['/usr/local/bin/node', 'script.js', '--version']
- setup事件- 每当 - cluster.setupMaster()被调用时触发。
- work.process是- child_process.fork()创建的进程
- 在主进程中,访问 - cluster.workers获得工作进程信息。其是一个哈希表,储存了活跃的工作进程对象,使用- id作为键名。只能在主进程中访问
- internal/cluster/child.js规定了工作进程的行为,这里定义了- cluster.worker,保存工作进程对象的引用。 在工程进程中可访问,指向当前工作进程。- 疑问
- workerProcess由- child_process.fork()创建- 'internalMessage'事件怎么触发,返回的参数- message和- handle是什么?
- 其 send方法干了啥?
 - 见 - child_process异步源码解析文章- fork部分,- setupChannel(child, ipc)