2.1 多进程模型:主进程与渲染进程


2.1 多进程模型:主进程与渲染进程

本节摘要:Electron 应用由一个主进程和若干渲染进程构成:主进程是应用入口,掌管生命周期、窗口创建与全部原生 API;每个 BrowserWindow 对应一个渲染进程,只运行 Web 内容。本节讲清两者的职责边界、进程树的生长方式、以及"窗口崩了应用为什么还活着"背后的稳定性设计。

一份岗位说明书

打开任务管理器,任何一个 Electron 应用都会露出一串进程。要读懂这串名单,先给两个核心岗位各发一份说明书。

主进程:全应用只有一个,由你在主进程入口文件里启动。它是与操作系统对话的唯一代表——应用图标、菜单栏、Dock 行为、文件关联、协议注册,全部经它之手。所有需要权限的原生模块(对话框、系统托盘、电源监控、原生通知)也只在这个进程可用。它跑在 Node.js 环境里,但没有 DOM——主进程里不存在 document,任何操作界面的企图都是方向性错误。

渲染进程:每一个窗口(准确说是每个 BrowserWindow 的 webContents)对应一个独立进程,跑着自己的 Chromium 渲染管线。DOM、CSS、Canvas、fetch,Web 的一切照常。它不碰原生 API(默认配置下连 Node 都没有),想读文件、弹系统对话框,只能向主进程发 IPC 请求。

为什么强制分离?两个动机。其一是稳定性:进程隔离意味着一个窗口的渲染崩溃(恶性插件、失控脚本、显存爆掉)只死那一个窗口,主进程捕获事件后可以重建它。其二是安全:能力集中在主进程,页面成为可丢弃、可怀疑的组件,被攻破时损失有边界。这两条动机会在第四章反复兑现。

// 主进程:展示"窗口崩溃不等于应用崩溃" const { app, BrowserWindow } = require('electron'); function createWindow() { const win = new BrowserWindow({ width: 960, height: 640 }); win.loadFile('index.html'); // 渲染进程崩溃(假死/被杀)时触发,应用本身仍在运行 win.webContents.on('render-process-gone', (_e, details) => { console.log('渲染进程退出,原因:', details.reason); // crashed / oom / killed ... // 常见策略:提示用户并重建窗口,而不是让应用直接退出 // win.destroy(); createWindow(); }); // 页面无响应(事件循环被长时间占死)时触发 win.webContents.on('unresponsive', () => { console.log('页面失去响应,可以弹框让用户选择等待或重载'); }); } app.whenReady().then(createWindow);

这段代码把"分层容错"具象化了:渲染层的问题被主进程接住,处置权在你。原生应用想做到同样的事要自己搭进程管理,Electron 把它做进了地基。

进程树怎么长

窗口不止一个时,进程树随之生长。用户在设置窗口里点开一个"帮助文档"窗口,就多一个渲染进程;开着 DevTools 还会有工具进程;Chromium 自身还会按需起 GPU 进程、网络服务进程、音频服务进程。经验规律:一个窗口约等于一个渲染进程,重界面(大量 CSS 合成、视频、WebGL)还会雇额外的 GPU 进程干活

图:一个双窗口应用的进程树

图:一个双窗口应用的进程树

理解进程树不只是满足好奇心,它直接指导两件事。第一是性能归因:看到内存高,要先分辨是哪个进程高——主进程膨胀通常是泄漏或缓存失控,渲染进程高可能是 DOM 规模失控,GPU 进程高多半与合成层滥用有关,处方完全不同(第五章展开)。第二是资源控制:窗口越多进程越多,隐藏窗口该销毁还是保留,直接影响常驻内存(5.4 节给出策略表)。

职责边界速查

事项 主进程 渲染进程
创建/销毁窗口、菜单、托盘 可以,且只能在这里 不行
读写本地文件、调用系统能力 可以 只能经 IPC 请求主进程代办
操作 DOM、渲染界面 不行 职责所在
访问页面里的 JS 变量 不行(进程隔离) 天然可以
弹原生文件对话框 dialog 模块直接用 经 IPC 由主进程弹

这张表里最容易犯错的行是最后一行和第一行。新人常在页面里直接找 dialog 模块用(找不到,默认配置下连 require 都没有),或者在主进程里想改页面标题(改不了,得发消息让页面自己改)。记住口诀:系统的事问主进程,界面的事页面办,两头都别越界。

这张口诀还有一条常被漏掉的推论:职责分离是双向的护栏,不是单向的围墙。主进程不碰 DOM,不仅因为技术上做不到,更因为架构上不该——一旦主进程开始"关心"界面的细节(根据某个按钮的状态决定业务逻辑),两个进程的知识就耦合了,后续任何界面改版都会波及主进程,测试与排障的复杂度随之失控。健康的形态是主进程提供动词化的能力(导出、读取、注册),页面自主决定何时调用、如何呈现结果。判断耦合是否越界有个朴素的标准:主进程的代码里如果出现了界面控件的名词(按钮、标签页、输入框),多半已经越界了,把决策权还给页面,主进程只保留动作本身。

补一个 DevTools 里就能做的小实验帮助巩固进程观:打开任一 Electron 应用的 DevTools,在 Performance monitor 面板里能同时看到该渲染进程的 DOM 节点数、JS 堆大小与事件循环延迟——注意这些指标全部只描述"本进程",隔壁窗口的任何抖动在这里毫无体现。看惯了这张单进程视图,再回头看任务管理器里的整串进程,多进程模型就从概念变成了一眼可见的物理现实。

本节要点回顾

  • 一与多:主进程全应用唯一,渲染进程每窗口一个,另有 GPU 等辅助进程按需补充;
  • 分离的两动机:崩溃隔离保稳定,能力收口保安全;
  • 容错钩子:render-process-gone 与 unresponsive 让渲染层故障可被主进程接住处置;
  • 进程树即诊断地图:内存归因先定位进程,再谈优化;
  • 边界口诀:主进程无 DOM,渲染进程无原生,跨界走 IPC。

下一节往下挖一层:Chromium 和 Node 这两套本来互不相识的运行时,是怎么被缝到一起的。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U