以浏览器多进程的角度解构页面渲染的整个流程

一、前言

页面在浏览器上的渲染并不是一个一条线的过程,而是多进程架构下协同的结果,本文从浏览器多进程的角度解构从url输入到页面渲染的整个流程的解析。

二、浏览器的多进程架构

1.进程与线程之间的关系

在操作系统中,进程是资源分配的最小单位,而线程是 CPU 调度的最小单位。

为了更好地理解浏览器架构,我们可以从以下三个维度来拆解它们的关系:

A. 包含与归属:工厂与工人

进程是容器:一个进程好比一个工厂车间,它拥有独立的内存空间、数据集和系统资源(如网络句柄、文件描述符)。

线程是执行者:线程是车间里的工人。一个进程可以包含多个线程,它们协同完成复杂的任务

B. 共享与隔离:内存的边界

进程间相互隔离:为了保证系统的稳定性,进程与进程之间的内存是完全隔离的如果一个渲染进程(Tab页)因为代码崩溃了,它不会影响到浏览器主进程或其他 Tab 页

线程间资源共享:同一个进程内的所有线程都可以访问该进程的内存空间 。这意味着 JS 引擎线程可以轻松地读取到由网络线程下载并存放在内存中的数据

C. 协同与竞争:互斥锁的由来 ★★★

这是理解"为什么 JS 会阻塞渲染"的关键点:

在同一个渲染进程内,GUI 渲染线程和 JS 引擎线程是互斥的。

原因:因为 JavaScript 脚本具有修改 DOM 的能力。如果两者同时运行,GUI 线程正在绘制一个 DOM 节点,而 JS 线程同时把它删除了,就会导致渲染结果不可预期。因此,浏览器规定:当 JS 引擎工作时,GUI 渲染线程会被挂起。

2. 为什么浏览器要采用"多进程"而非"单进程多线程"?

在早期的浏览器中,所有功能都运行在一个进程里。现代浏览器演进为多进程架构,主要是为了解决三个核心痛点:

稳定性: 在单进程下,任何一个线程的崩溃(如 Flash 插件卡死或复杂的脚本死循环)都会导致整个浏览器瘫痪。多进程架构下,"一个 Tab 一个进程" 实现了故障隔离。

安全性: 浏览器通过沙箱 机制将渲染进程锁起来。由于渲染进程运行的是不受信任的第三方脚本,沙箱让它无法直接读写本地文件或调用系统 API。所有的敏感操作必须通过 IPC(进程间通信) 告知浏览器主进程,由主进程进行权限审查后代为执行。

流畅性: 多进程可以更充分地利用多核 CPU 的并行计算能力 。同时,某些耗时的操作(如网络下载、插件运行)被抽离到独立进程中,不会占用渲染进程的主线程

3.浏览器的主要进程及其进程下的线程(4 个核心进程 和 2 个辅助/动态进程)

① 浏览器主进程

它是浏览器的核心,也是所有进程的父进程。

职责:

界面显示:负责地址栏、前进后退按钮、书签栏等浏览器"外壳"的 UI。

用户交互:监听鼠标点击、键盘输入

进程管理:负责创建、销毁和协调其他子进程

存储功能:负责管理 Cookie、本地存储(LocalStorage)等磁盘读写

核心线程:

  • UI 线程:负责绘制浏览器外壳(地址栏、书签栏、窗口控制按钮)。
  • I/O 线程:负责与其他进程进行 IPC 通信。(进程管理)

它负责管理子进程。当你点击关闭按钮,是 UI 线程捕捉到信号,通知浏览器进程去销毁对应的渲染进程

② 网络进程

原本是浏览器主进程中的一个线程,为了提升稳定性和安全性,现代 Chrome 将其独立为进程,专门负责处理与外部世界的资源交换。

职责:

资源加载:负责发起所有的网络请求 (HTTP/HTTPS),并接收服务器返回的数据

协议解析:解析 HTTP 响应头、状态码,处理 301/302 重定向

DNS 解析:将域名映射为 IP 地址

缓存管理:根据 HTTP 头部信息(如 Cache-Control)判断并管理磁盘/内存中的网络资源缓存

安全校验:拦截恶意 URL 访问(如 Safe Browsing 安全检查)。

Cookie 处理:负责 HTTP 响应中 Set-Cookie 的解析,以及请求时 Cookie 字段的自动注入

核心线程:

网络协议栈线程:这是最忙碌的"工人",负责处理 TCP 握手、TLS 加密解密、以及 HTTP/1.1、H2、H3 协议的处理。

DNS 解析线程:专门负责寻找域名背后的 IP 地址,并维护 DNS 缓存。

Socket 管理线程:管理与不同服务器之间的长连接(Connection Pool)。

③ 渲染进程

这是前端代码真正运行的"车间",也是浏览器安全机制的核心。每个 Tab 标签页通常拥有一个独立的渲染进程,它运行在"沙箱"中,无法直接访问系统资源。

核心职责:

解析与构建:将 HTML、CSS 字节流转化为浏览器能理解的 DOM 树和 CSSOM 树

脚本执行:运行 JavaScript 代码,处理用户交互逻辑

布局与绘制:计算元素的大小位置,并生成最终的像素图像传给 GPU 进程

五大核心线程:

A.GUI 渲染线程

职责:负责解析 HTML、CSS,构建 DOM 树、CSSOM 树、布局树和绘制。

特点:当界面需要重绘(Repaint)或由于某些操作引发回流(Reflow)时,该线程就会执行

B.JS 引擎线程

职责:负责解析 JavaScript 脚本,运行代码

核心痛点(互斥机制):JS 引擎线程与 GUI 渲染线程是互斥的。如果 JS 执行时间过长,就会导致页面渲染加载阻塞,出现掉帧或卡顿。

面试亮点:为什么互斥?因为 JS 拥有修改 DOM 的权限。如果两者并行,可能会出现"GUI 正在画背景,而 JS 删除了该节点"的竞态问题。

C.事件触发线程

职责:归属于浏览器而不是 JS 引擎,用来控制事件循环(Event Loop)。

工作机制:当事件被触发(如点击、AJAX 完成)时,该线程会将对应的回调任务加入到任务队列的末尾,等待 JS 引擎空闲时处理。

D.定时器触发线程

职责:负责 setTimeout 与 setInterval 的计时

存在的意义:因为 JS 引擎是单线程的,如果处于阻塞状态就无法计时。因此需要独立线程计时,计时完毕后再通知事件触发线程将回调推入队列。

注意:W3C 标准规定,setTimeout 的间隔时间低于 4ms 会被自动设为 4ms。

E.异步 HTTP 请求线程

职责:在请求发起后,通过浏览器分配一个线程专门负责监控网络状态

工作机制:当请求状态变更(如成功返回)时,如果设有回调函数,该线程就会通知"事件触发线程"将回调放入任务队列。

F. 合成线程 专门负责处理页面的分层和图像合成,不占用主线程(GUI渲染线程和JS引擎线程)

职责:

接收指令:主线程完成布局和绘制列表后,将这些信息提交给合成线程

图层切片:将页面图层划分为大小固定的图块(Tiles),优先栅格化视口内的内容

调度栅格化:配合 GPU 进程将图块转换为位图(像素点)

响应交互:直接处理页面的滚动 (Scroll) 和 缩放 (Zoom),而无需经过主线程

核心优势(独立性)

非阻塞交互:由于合成线程与 JS 引擎线程、GUI 渲染线程不互斥。这意味着即使 JS 引擎正在运行一个死循环导致页面卡死,你依然可以流畅地滚动页面。

硬件加速:它是利用 GPU 资源的核心入口,通过处理 transform、opacity 等属性,实现无需重排重绘的高性能动画。

④ GPU 进程 (GPU Process)

最初仅用于处理 3D 图形,但随着现代网页对流畅度要求的提高,它已成为网页"排版合成"与"像素上色"的物理支柱。

职责:

硬件加速:将合成线程提交的图块由逻辑指令转换为显卡可识别的位图

复合渲染:负责将来自不同进程(如浏览器进程的 UI、渲染进程的网页内容)的位图进行混合,最终绘制到显示器屏幕上。

核心线程:

GPU 渲染线程:与显卡驱动直接通信,执行真正的绘制操作

为什么独立?

浏览器将 GPU 独立为进程,主要是因为图形处理涉及到复杂的操作系统底层调用和硬件驱动。驱动程序通常不如系统核心稳定,一旦 GPU 任务崩溃,浏览器只需重启该进程即可,而不会导致整个浏览器或所有标签页"黑屏"或死机。

⑤ 插件进程 (Plugin Process)

专门用于运行如 Flash、Silverlight 等第三方插件的进程。

职责:

隔离风险:插件往往由第三方开发,稳定性差且极易存在安全漏洞

物理隔离:确保即便插件崩溃或被劫持,其破坏力也仅限在该进程内部,不会波及渲染进程(你的网页)或主进程。

随着 Chrome 彻底停止对 Flash 的支持,现代网页中插件进程已较少出现。注意不要将"插件"与"扩展"混淆。

⑥ 扩展进程 (Extension Process)

地位:为你安装的浏览器扩展程序(如 Vue Devtools, AdBlock, 翻译插件)提供独立的运行空间。

职责:

独立运行:确保扩展程序的 JS 逻辑不会占用网页渲染进程的 CPU 资源

权限管控:浏览器进程会根据扩展申明的权限严格控制扩展进程对网页内容(DOM)或系统 API 的访问

思考题

渲染进程有GUI线程负责对html css js的解析和渲染,主进程的UI线程和gpu进程的gup加速线程也有类似功能,为什么要这样设计?

A. 渲染进程:逻辑计算的核心

虽然它叫"渲染进程",但它大部分时间在做逻辑转换

GUI 线程的工作:它把代码字节流变成 DOM/CSSOM。最重要的是,它计算出 Layout(布局)。 它告诉浏览器:"这里有一个 100x100 的红色方块"。

它不直接画图:GUI 线程并不直接控制显示器像素,它生成的只是"绘制指令(Paint Records)"。

B. GPU 进程:硬件加速的真相

以前浏览器确实靠 CPU 画图(软件渲染),但 CPU 处理图形太慢了。

GPU 加速线程:它接收来自合成线程的指令 。因为 GPU 擅长并行处理大量像素,它把渲染进程算好的"图块"直接转为屏幕上的像素。

独立性:把 GPU 独立出来是为了防崩溃。图形驱动非常脆弱,如果 GPU 线程在渲染进程里,一个复杂的 3D 效果挂了,你的网页就崩了。

C. 主进程(UI 线程):窗口的守护者

为什么主进程也要参与"显示"?

非网页区域的渲染:网页之外的区域(地址栏、书签栏、前进后退按钮)不受渲染进程控制。

最终合成:这是一个关键点。屏幕上显示的内容 = 浏览器外壳位图 + 网页内容位图

协作流程:GPU 进程会把画好的网页内容位图交给主进程,主进程把自己的 UI 位图叠上去,最后由主进程指挥显示器把这整张图显示出来。

一个具体的场景:改变 background-color

渲染进程 (JS/GUI 线程):JS 修改了 CSS,主线程重新计算样式,发现颜色变了,生成一份新的"绘制列表"。

渲染进程 (合成线程):拿到列表,把任务分块,发给 GPU 进程。

GPU 进程 (GPU 线程):调用显卡硬件,把受影响的像素点重新喷色,生成位图。

浏览器主进程 (UI 线程):把这张新的位图放在浏览器窗口的"白板"区域显示。

三、从url输入到页面渲染的全流程(结合浏览器多进程架构)

整个流程实质上是多个独立进程在浏览器主进程的调度下,通过 Mojo IPC(进程间通信) 进行的一场数据与控制权的接力。

1. 导航触发:浏览器主进程的调度与拦截

输入预处理:UI 线程 拦截地址栏输入。若为非 URL 字符串,调用搜索引擎封装 URL;若为合法 URL,则直接进入导航逻辑

BeforeUnload 拦截:如果当前已存在页面,主进程通过 IPC 向当前渲染进程发出信号。渲染进程执行 JS 逻辑并返回结果。为了防止渲染进程无响应导致导航卡死,主进程会对这一过程设置 Timeout 阈值。

启动网络指令:UI 线程发起一个指向 网络进程 的 IPC 请求

这里详细解释一下BeforeUnload和启动网络指令的过程及优化------

① BeforeUnload 拦截:给旧页面"交代遗言"的机会

当你点击一个链接或在地址栏回车时,当前的网页(旧页面)还没销毁。浏览器必须先询问它:"你还有没处理完的事吗?"

IPC 信号是什么? 浏览器主进程(管理窗口的)发现你要跳走了,它会发一个 IPC(进程间通信)消息 给当前网页所在的渲染进程

渲染进程在做什么? 渲染进程接收信号后,会检查 JS 代码里有没有监听 beforeunload 事件。比如你在写博客,还没保存,JS 就会弹出一个对话框:"系统可能不会保存您所做的更改。确定要离开吗?"

为什么需要 Timeout(超时)阈值? 这是为了防死锁。如果旧页面的渲染进程崩了,或者 JS 写了个死循环(例如 while(true){}),它就无法回复主进程。如果没有超时机制,你的浏览器地址栏就会永远卡在那里。

底层逻辑: 主进程会启动一个定时器(比如几秒钟)。如果渲染进程在规定时间内没回话,主进程会认为这个渲染进程"挂了",直接强行掐断它的生命周期,强制开始加载新页面。

②启动网络指令:外交部正式出航

一旦旧页面被处理完(或者超时了),浏览器主进程就要去互联网上拿新页面的数据了。

UI 线程发起请求: 此时,主进程里的 UI 线程(负责处理地址栏、按钮点击的那个工人)会整理好目标 URL、Cookie、请求头等信息。

指向网络进程的 IPC 请求: 在现代 Chrome 中,主进程自己不负责下载。它会把刚才整理好的"请求包"通过 IPC 扔给 网络进程

形象点说: 主进程(CEO)给网络进程(外交部)打了个电话:"喂,去帮我把 github.com 的 HTML 字节流取回来。"

③为什么这两步是"并行的优化点"?

这里有一个非常硬核的亮点:现代浏览器并不会等 beforeunload 彻底结束才去发起网络请求。

为了快,浏览器通常会采取 并行策略:

一边让主进程询问旧页面是否要离开。

一边同步通知网络进程去进行 DNS 解析 和 建立连接。

如果用户最后点击了"取消离开",浏览器就把刚发起的网络请求掐断。如果用户确定离开,此时网络连接可能已经建好了,网页秒开。这就是所谓的 "导航预加载"思想。

2. 网络资源获取:网络进程的"外交"与"初筛"

当网络进程接到主进程的指令后,它开始在互联网上为网页寻找材料。

A. 物理链路的打通(建立连接)

浏览器缓存:首先检查网络进程内存中存储的 DNS 记录(通常缓存 1 分钟)

DNS 与握手如果浏览器缓存未命中网络进程会去查 IP 地址(DNS),然后进行 TCP 三次握手。如果是 HTTPS,还要进行 TLS 加密握手。

亮点:为了快,浏览器会维护一个 连接池。如果最近刚访问过这个域名,它会直接复用之前的"管道",省去握手时间。

B. 响应头的解析与"重定向"黑箱

内部消化重定向如果服务器返回 301/302(重定向),网络进程不会跑回去告诉主进程,而是自己在内部重新发起新的请求。

逻辑意义:对主进程和渲染进程来说,它们只关心最终拿到的结果,中间转了几次弯(重定向),网络进程在底层偷偷帮你处理好了。

C. 核心:响应体的"首包"嗅探

这是全流程中最精妙的地方。当网络进程收到服务器返回的第一份数据包(通常是前几个字节)时

确定身份:网络进程会查看 Content-Type。

如果是 text/html,它就知道:"正主来了,准备通知渲染进程干活"。

如果是 application/octet-stream,它会意识到:"这是一个下载任务",于是把请求转交给下载管理器,导航流程在此终止。

建立数据管道:

核心机制:一旦确认是 HTML,网络进程不会等整个网页下载完。

它会建立一条"数据长管"。管子的这头在网络进程(继续下载后续字节),管子的那头直接插进未来的渲染进程。

它的意义:实现"边下载边解析",极大地缩短了白屏时间。

3. 提交导航:控制权从主进程移交给渲染进程

这是导航阶段最核心的"状态切换"点,标志着页面正式从旧地址切换到新地址。

进程分配:主进程根据 Site Isolation 策略分配渲染进程。如果是同站跳转,可能复用原有进程;否则启动新进程。

Commit 指令:主进程发送 CommitNavigation 消息给目标 渲染进程。

数据交接:主进程会将网络进程中那个 Data Pipe 的句柄随指令发送给渲染进程。

确认反馈:渲染进程收到句柄后,直接从管道读取数据流。一旦开始解析,渲染进程向主进程发送 DidCommitProvisionalLoad。

状态切换:主进程收到反馈后,执行 UI 状态更新(更新地址栏、重置历史记录、刷新前进按钮)。此时旧页面正式被销毁。

4. 渲染流水线:渲染进程与 GPU 的像素产出

在渲染进程接收到"数据管道"的句柄后,内部的主线程、合成线程 与 GPU 进程 开始高度协同。

A. 解析与构建:将字节转化为结构

流式解析:主线程无需等待 HTML 下载完成。通过 Data Pipe,每接收到一个数据包,GUI 渲染线程就会立即启动解析,边下载边构建 DOM 树。

样式计算(CSSOM):主线程解析 CSS 样式,计算出每个 DOM 节点的最终样式

亮点(互斥机制):此阶段若遇到 <script>,主线程会挂起 GUI 渲染线程,切换到 JS 引擎线程。这种互斥确保了 JS 在修改 DOM 时不会产生渲染竞态。

B. 几何计算:确定空间坐标

布局树(Layout Tree)构建:主线程将 DOM 与 CSSOM 合并。它会过滤掉 display: none 的节点,仅保留可见元素。

几何量算:主线程精确计算每个元素在三维空间中的 (x, y) 坐标、宽高及层级

产物:一棵包含所有几何信息的布局树。

C. 记录与图层化:生成施工图纸

分层:为了处理 3D 转换(transform)或滚动,主线程会根据属性将页面拆分为多个图层。

绘制记录(Paint):主线程并不直接画图,而是将每个图层的绘制逻辑拆解为一个个指令(如:"在此处画正方形","在彼处填充红色")。

产物:一份名为 绘制记录(Paint Records) 的逻辑清单。

D. 栅格化与合成:像素的工业产出

任务此时从主线程移交给合成线程,进入真正的硬件加速阶段。

切片(Tiling):合成线程将巨大图层划分为固定大小的 图块(Tiles),优先处理视口(用户肉眼可见区域)内的内容。

栅格化:

合成线程通过 IPC 向 GPU 进程 发出指令。

GPU 进程 利用显卡的并行计算能力,将图块指令转化为显存中的位图。

复合与上屏:

合成线程收集所有图块位图,生成一份"指引(Compositor Frame)"。 浏览器主进程 接收该指引,将网页位图与浏览器外壳(地址栏等)进行叠加,最终由 GPU 刷新到屏幕上。

为什么要把"合成"独立出来?

非阻塞滚动:当主线程因为运行复杂的 JavaScript 而卡死时,合成线程 依然可以独立工作。它能直接利用 GPU 显存里已有的位图进行位移偏移,这就是为什么即便网页脚本卡顿,你依然能流畅滑动(Scroll)页面的原因。

硬件加速:通过 transform 或 opacity 做的动画,直接在合成阶段完成,不触发主线程的"重排"或"重绘",实现了真正的性能最优。

四、流水线视角的重排、重绘与合成

理解了多进程协作的渲染流水线后,我们就能从底层逻辑解释:为什么有的代码会让页面卡顿,而有的代码却能实现 60fps 的丝滑动画? 关键在于你的操作强迫流水线"回溯"到了哪一步。

1. 重排

触发原因:修改了影响几何空间的属性(如 width, height, margin, padding, border, display 等),或调整浏览器窗口大小。

流水线回溯:

主线程:必须重新经历 样式计算 -> 布局 -> 图层分层 -> 生成绘制列表 。

合成线程:重新进行图块划分。

GPU 进程:重新进行栅格化和位图上传。

这是开销最大的操作,因为它触发了全量流水线,且深度依赖主线程的计算压力。

2. 重绘

触发原因:修改了不影响布局、仅影响视觉外观的属性(如 color, background-color, visibility, box-shadow 等)。

流水线回溯:

主线程:跳过布局和分层,直接重新生成 绘制记录。

合成/GPU 进程:重新进行栅格化。

开销中等。虽然避开了几何几何计算,但依然需要主线程生成指令并触发 GPU 重新喷色。

3. 合成 (Composite):硬件加速的"超车道"

触发原因:使用 CSS 的 transform(位移、缩放、旋转)或 opacity。

流水线表现:

主线程完全不参与。

合成线程:直接接收指令,在 GPU 中利用已有的图块位图进行矩阵变换。

开销极低。这是多进程架构带来的最大红利------动画直接在合成线程与 GPU 进程间通讯,即使此时 JS 引擎在主线程里跑死循环,合成动画依然能流畅运行。

五、总结

通过对浏览器多进程架构及渲染流水线的深度解构,我们可以发现,从输入 URL 到页面呈现,本质上是一场多进程间的"接力赛"与流水线上的"精密加工"。

1. 核心链路回顾

我们可以将整个漫长的流程浓缩为四个关键的瞬间:

主进程:拦截输入,启动导航,指挥网络部出航。

网络进程:打通链路,嗅探内容,并建立指向未来的数据管道。

渲染进程-主线程:将字节流转化为 DOM/CSSOM,并在几何计算中确定每一个像素的坐标。

合成线程 & GPU:利用硬件加速,将逻辑指令转化为位图,实现最终的像素上屏。

2. 给前端开发的性能启示

理解了这套底层机制,我们对"性能优化"的理解便不再流于表面,而是进化为一种流水线思维:

保护主线程:GUI 渲染与 JS 执行的互斥性告诉我们,长任务是掉帧的元凶。我们应当利用 Web Workers 或时间切片来释放主线程。

善用合成器:优先使用 transform 和 opacity 实现动画,本质上是在利用多进程架构的红利,绕过拥挤的主线程,走 GPU 加速的"超车道"。

尊重重排规律:减少对 offsetWidth 等属性的频繁读取,实质上是在保护流水线不被"强制同步布局"打断,避免昂贵的重复计算。

3. 写在最后

浏览器作为现代最复杂的软件之一,其多进程架构是稳定性、安全性和高性能巴巴博弈后的终极方案。

作为开发者,理解底层是为了更自由地构建上层。 当你再次打开浏览器的 Performance 面板,看到那些交织的进程与线程曲线时,你看到的不再是枯燥的数据,而是一场由数万行 C++ 代码支撑、毫秒必争的协作交响乐。

相关推荐
恋猫de小郭40 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端