我们前面说过,child_process 模块创建的子进程是被主进程统一管理的,如果主进程挂了,所有的子进程也会受到影响一起挂掉,但其实使用多进程一方面为了提高处理任务的效率,另一方面也是为了当一个进程挂掉时还有其他进程可以继续工作,不至于整个应用挂掉,这样的例子非常多,比如 Chrome 浏览器的选项卡,比如 VSCode 编辑器运行时都会同时开启多个进程同时处理任务,其实在 spawn 创建子进程时,也可以实现子进程的独立,即子进程不再受主进程的控制和影响。
// 文件:process.js const { spawn } = require("spawn"); const path = require("path"); // 创建子进程 let child = spawn("node", ["sub_process.js"], { cwd: path.join(__dirname, "test"), stdio: "ignore", detached: true }); // 与主进程断绝关系 child.unref();
// 文件:~test/sub_process.js const fs = require("fs"); setInterval(() => { fs.appendFileSync("test.txt", "hello"); });
要想创建的子进程独立,需要在创建子进程时配置 detached 参数为 true,表示该子进程不受控制,还需调用子进程的 unref 方法与主进程断绝关系,但是仅仅这样子进程可能还是会受主进程的影响,要想子进程完全独立需要保证子进程一定不能和主进程共用标准输入、标准输出和错误输出,也就是 stdio 必须设置为 ignore,这也就代表着独立的子进程是不能和主进程进行标准进程通信,即不能设置 ipc。
fork 实现多进程
1、fork 的使用
fork 也是 child_process 模块的一个方法,与 spawn 类似,是在 spawn 的基础上又做了一层封装,我们看一个 fork 使用的例子。
// 文件:process.js const fork = require("child_process"); const path = require("path"); // 创建子进程 let child = fork("sub_process.js", ["--port", "3000"], { cwd: path.join(__dirname, "test"), silent: true }); child.send("hello world");
// 文件:~test/sub_process.js // 接收主进程发来的消息 process.on("message", data => console.log(data));
fork 的用法与 spawn 相比有所改变,第一个参数是子进程执行文件的名称,第二个参数为数组,存储执行时的参数和值,第三个参数为 options,其中使用 slilent 属性替代了 spawn 的 stdio,当 silent 为 true 时,此时主进程与子进程的所有非标准通信的操作都不会生效,包括标准输入、标准输出和错误输出,当设为 false 时可正常输出,返回值依然为一个子进程。
fork 创建的子进程可以直接通过 send 方法和监听 message 事件与主进程进行通信。
2、fork 的原理
其实 fork 的原理非常简单,只是在子进程模块 child_process 上挂了一个 fork 方法,而在该方法内调用 spawn 并将 spawn 返回的子进程作为返回值返回,下面进行简易实现。
// 文件:fork.js const childProcess = require("child_process"); const path = require("path"); // 封装原理 childProcess.fork = function (modulePath, args, options) { let stdio = options.silent ? ["ignore", "ignore", "ignore", "ipc"] : [0, 1, 2, "ipc"]; return childProcess.spawn("node", [modulePath, ...args], { ...options, stdio }); } // 创建子进程 let child = fork("sub_process.js", ["--port", "3000"], { cwd: path.join(__dirname, "test"), silent: false }); // 向子进程发送消息 child.send("hello world");
// 文件:~test/sub_process.js // 接收主进程发来的消息 process.on("message", data => console.log(data)); // hello world
spawn 中的有一些 fork 没有传的参数(如使用 node 执行文件),都在内部调用 spawn 时传递默认值或将默认参数与 fork 传入的参数进行整合,着重处理了 spawn 没有的参数 silent,其实就是处理成了 spawn 的 stdio 参数两种极端的情况(默认使用 ipc 通信),封装 fork 就是让我们能更方便的创建子进程,可以更少的传参。
execFile 和 exec 实现多进程
execFile 和 exec 是 child_process 模块的两个方法,execFile 是基于 spawn 封装的,而 exec 是基于 execFile 封装的,这两个方法用法大同小异,execFile 可以直接创建子进程进行文件操作,而 exec 可以直接开启子进程执行命令,常见的应用场景如 http-server 以及 weboack-dev-server 等命令行工具在启动本地服务时自动打开浏览器。
// execFile 和 exec const { execFile, exec } = require("child_process"); let execFileChild = execFile("node", ["--version"], (err, stdout, stderr) => { if (error) throw error; console.log(stdout); console.log(stderr); }); let execChild = exec("node --version", (err, stdout, stderr) => { if (err) throw err; console.log(stdout); console.log(stderr); });