5

Chrome 的多进程架构

进程 & 线程

在谈浏览器多进程架构之前,我们先聊聊进程和线程的概念。

进程是系统进行资源调度和分配的的基本单位,一个进程可以认为是一个程序的运行实例。启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,我们把这样的一个运行环境叫进程。

线程是依附于进程的,是操作系统可识别的最小执行和调度单位,而进程中使用多线程并行处理能提升运算效率,多线程可以并行处理任务,但是线程是不能单独存在的,它是由进程来启动和管理的。我们最熟悉的 JS 的运行就是线程这个维度。

下面是进程和线程间的一些区别:

  • 一个线程依附于一个进程,一个进程可以有多个线程;
  • 线程之间共享进程中的数据。
  • 进程中的任意一线程执行出错,都会导致整个进程的崩溃,而进程之前不会相互影响。
  • 当一个进程关闭之后,操作系统会回收进程所占用的内存。
  • 进程之间的内容相互隔离,只能通过 IPC 通信。

我觉得知乎上一个用火车比喻进程和线程的例子比较形象:进程好比火车,线程好比车厢,一辆火车有多节车厢,不同的火车之间互不干扰,但是一节车厢失火会殃及多节车厢。

Chrome 架构

image.png

Chrome 浏览器包括:1 个 Browser 主进程、1 个 GPU 进程、1个 Utility 进程、多个 Renderer 进程和多个 Plugin 进程。

  • Browser 主进程(浏览器进程):主要负责界面显示、用户交互、子进程管理,同时还包含网络请求和文件访问等。
  • GPU 进程:与其他进程隔离处理 GPU 任务。
  • Renderer 进程(渲染进程):核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
  • Plugin 进程:主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。

浏览器渲染机制

渲染进程的核心工作是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页。在这个工作过程中,输入的 HTML 经过一些子阶段,最后输出像素。按照渲染的时间顺序,这些子阶段大致可分为:构建 DOM 树、计算样式、布局、分层、绘制、分块、栅格化和合成。

image.png

构建 DOM 树

当渲染进程开始接收 HTML 数据时,主线程开始解析 HTML 并将其转换为浏览器能够理解的 DOM 树结构。

image.png

在 DOM 树的解析过程中,如果遇到 img、css 或者 js 资源时,主线程会向 Browser 主进程的网络线程发送请求以获取对应的资源。

当解析的过程中遇到 <script> 标签时,主线程会暂停 HTML 的解析,从而进行 js 代码的加载、解析和执行。因为 js 代码中可能涉及对页面结构的修改,主线程必须等待 js 运行才能恢复对 HTML 文档的解析。因此我们可以通过在 <script> 标签上加上 async 或者 defer 属性来异步加载执行 js 代码,避免 js 阻塞 HTML 的解析。

样式计算

样式计算的目的是为了计算出 DOM 节点中每个元素的具体样式,在计算过程中需要遵守 CSS 的继承和层叠两个规则,这个阶段大体可分为三步来完成:

  • 把 CSS 转换为浏览器能够理解的结构:当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——StyleSheets;
  • 转换样式表中的属性值,使其标准化:例如 rem 这些属性,需要将所有值转换为渲染引擎容易理解的、标准化的计算值;
  • 计算出 DOM 树中每个节点的具体样式

image.png
在浏览器中,我们可以通过 Computed 面板查看当前节点的 Computed Style。

image.png

布局

有了 DOM 树和 DOM 对应的 Computed Style 之后还不足以显示页面,接下来还需要计算出 DOM 树中可见元素的几何位置,这个计算过程叫做布局。

image.png

Chrome 在布局阶段需要完成两个任务:创建布局树和布局计算。

  • 创建布局树:浏览器会遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中,而不可见的节点会被布局树忽略掉,如图中 span 这个元素被设置为 dispaly:none,这个元素会被布局树忽略;
  • 布局计算:有了布局树后,浏览器会计算布局节点的坐标位置;

分层

有了布局树后,对于一些简单页面已经具备绘制条件了,但是对于我们现代的页面来说,有很多复杂的效果,如一些复杂的 3D 转换、 z-index 做 z 轴排序等,对于这些场景为了页面展示的正确性,渲染引擎还会为特定的节点生成专用的图层,并生成一棵对应的图层树。

image.png

需要注意的是,并不是每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点所在的图层。最终每一个节点都会直接或者间接地从属于一个图层。

通常满足下面两点便会提升为一个单独的图层:

  • 拥有层叠上下文属性的元素会被提升为单独的一层。层叠上下文
  • 需要剪裁的地方也会被创建为图层(overflow)。

我们可以在浏览器的 layers 面板看到当前页面的分层情况:

image.png

绘制

在确定了 DOM 树、计算样式以及布局树仍然不足以绘制页面,这里还需要有明确的绘制顺序,在此过程中主线程会遍历布局树并创建绘制记录。

image.png

同样,我们可以在浏览器的 layers 面板看到当前页面对应图层的绘制记录:

image.png

光栅化(栅格化)

在确定了布局树并创建了图层以及对应的绘制顺序之后,主线程会将信息提交给合成线程。合成线程会去光栅化每一个图层。

由于我们浏览器的视口是有限的,但是页面的长度可能很长,有些图层可能超过视口很多,而用户对页面的感知是视口维度的,一次性渲染整个图层未免有些浪费,因此合成线程会对图层进行分块处理

有了图块之后,合成线程会将每一个图块发送到光栅线程(Raster thread),光栅线程会光栅化每一个图块并存在 GPU 内存中。在这个过程中,合成线程会优先选择视口内的图块提交给光栅线程。

image.png

合成和展示

光栅化完成后,合成线程会创建合成帧通过 IPC 通信提交给浏览器进程。浏览器进程接收到指令后会将内容绘制在内存中并展示在屏幕上。

image.png

至此,从接收 HTML 数据到页面的展示全流程就结束了。下面我们再结合浏览器的渲染流程看下什么是重排、重绘和合成。

重排、重绘和直接合成

重排

当我们通过 js 或者 css 属性更新了元素的几何属性,例如元素的宽度、高度,此时浏览器会重新触发布局并重新执行后面全部的渲染流程,因此,重排的开销是最大的。

image.png

重绘

当我们通过 js 或者 css 更新元素的绘制属性,例如元素的背景色、文字的颜色等,此时布局和分层阶段被省略,只执行后续的流程,因此重绘的开销相比重排会小很多。

image.png

直接合成

为什么我们为了避免重排和重绘而去采用 css3 的 transform 等属性呢?因为此时整个主线程的流程会被全部跳过,执行后续的流程,而后续的流程交给了在执行线程、光栅线程和 GPU 进程上执行没有占据主线程的资源,因此效率是最高的。

image.png

参考文章


玩弄心里的鬼
1.2k 声望1.1k 粉丝