本文基于谷歌浏览器的多进程结构 --内容参考b站up主卢克儿
进程
进程是操作系统进行资源分配和调度的基本单元,可以申请和拥有计算机资源,是程序的基本执行实体。
线程
线程是操作系统能够进行运算调度的最小单位,一个进程中可以并发多个线程,每条线程并行执行不同的任务
每个进程会分配独立的内存空间,进程间通过IPC管道通信。
谷歌浏览器的进程根据功能划分:
- 缓存进程
- GPU进程 --负责整个浏览器界面的渲染
- 插件进程 --控制网站使用的所有插件,比如flash
- 网络进程 --负责发起接收网络请求
- 缓存进程
- 渲染器进程
- 浏览器进程 -- 负责控制chrome浏览器除标签页外的用户界面,包括地址栏,书签,后退和前进按钮,以及负责与浏览器的其他进程协调工作,可设置进程模型
url输入后
浏览器进程的UI线程会捕捉你输入的信息, 如果是发起一个请求,则UI线程会启动一个网络线程来,请求DNS域名解析, 接着开始连接服务器获取数据,当网络线程获取数据以后,会通知UI线程, UI线程 会创造一个渲染器进程来渲染页面, 浏览器进程通过IPC管道将数据传递给渲染器进程,进入渲染阶段。
渲染阶段
当网络线程获取到数据后,网络线程通知UI线程,然后UI线程会创建一个渲染器进程,浏览器进程通过IPC管道将数据传递给渲染器进程,进入渲染流程,渲染器进程接到的数据是html,浏览器进程的核心任务就是把html,css,js,image等资源渲染成可以交互的web页面,渲染器进程的主线程将html进行解析,构造DOM数据结构,DOM也就是文档对象模型,是浏览器在其内部的表示形式,是web开发程序员可以通过js与之交互的数据结构和API,html首先经过tokeniser标记化,通过词法分析将输入的html内容解析成多个标记,根据标识后的标记进行DOM树构造,在DOM树构造过程中会创建document对象,然后以document为根节点的DOM树,不断进行修改,向其中添加各种元素,html代码中往往会引入一些额外的图片和CSS这些资源需要通过网络,下载或从缓存中直接加载,这些资源不会阻塞html的解析,因为它们不会影响DOM的生成,但当HTML标解析过程中遇到script标签,就会停止html解析流程,转而去解析并且执行js,这是因为浏览器不知道js执行是否会改变当前页面的HTML结构,如果js代码里用了document.write方法来修改html,之前的解析就没意义了,可以使用async或defer属性,来异步加载执行js,主线程需要解析css,并确定每个DOM节点的计算样式,知道DOM结构和每个节点的样式后,放在页面上的哪个位置,也就是节点的坐标及该节点需要,这个阶段被称为layout布局,主线程通过遍历dom和计算好的样式来生成layout Tree 节点都记录了下x,y坐标和边框尺寸,这里需要注意的一点是DOM tree和layout tree不一定是一一对应的,比如设置了display:none等,layout tree是和最后展示在屏幕上的节点是对应的,我们还需要知道以什么样的顺序绘制(paint),z-index会影响节点绘制,所以为了保证在屏幕上展示正确的层级,主线程遍历layout tree创建一个绘制记录表(paint record),该表记录了会绘制的顺序,这个阶段被称为绘制(paint),现在知道了文档的绘制顺序,就可以把这些信息转化成像素点显示在屏幕上,这种行为被称为栅格化
栅格化
最早使用了一直很简单的方式,只栅格化用户可视区(viewport)的内容,当用户滚动页面时,再栅格化更多的内容来填充缺失的部分,这种方式会导致展示延时,随着不断的优化升级,现在使用的是一种更为复杂的栅格化流程,叫做合成(composting),合成是一种将页面的各个部分,分成多个图层,分别对齐进行栅格化,并在合成器线程(compositor thread)的技术中单独合成进行合成页面,简单来说就是页面所有元素按照某种规则进行分图层,并把图层都栅格化好了,然后只需要把可视区的内容组合成帧展示给用户即可,主线程遍历layout tree ,生成layer(图层)tree,当layer tree生成完毕和绘制顺序确定后,主线程将这些信息传递给合成器线程,合成器线程将每个图层栅格化,由于一层可能像页面的整个长度一样大,因此合成器线程将它们切分为许多图块(tiles),然后将每个图块发送给栅格化线程,栅格化线程栅格化每个图块,并将它们存储在GPU内存中,当图块栅格化完成后,合成器线程将收集称为“draw quads”的图块信息,这些信息里记录了图块在内存中的位置和在页面的哪个位置绘制图块的信息,根据这些信息合成器生成了一个合成器帧,然后合成器prame(帧)通过ipc传送给浏览器的进程,接着浏览器进程将合成器帧传送到GPU,然后GPU渲染展示到屏幕上,当你的页面发生变化,比如你滚动了页面,都会生成一个新的合成器帧,新的帧再传给GPU,然后再次渲染到屏幕上
重绘和重排
当我们改变一个元素的尺寸位置属性时,会重新进行样式计算(computed style),布局(layout)绘制(paint)以及后面的所有流程,这种情况称为重排,当我们改变某个元素的颜色属性时,不会重新触发布局,但还是会触发样式计算和绘制,这就是重绘,重排和重绘都会占用主线程,js也运行在主线程上,就会出现抢占执行时间的问题,如果你写了一个不断导致重绘重排的动画,浏览器则需要在每一帧都运行样式,计算布局和绘制的操作。 当页面以每秒60帧的刷新率时(每帧16ms),用户才不会感到页面卡顿,如果你在运行动画时还有大量的JS,任务需要执行,因为布局,绘制和js执行都在主线程运行的,当在一帧的时间内布局和绘制结束后,如果还有剩余时间,js就会拿到主线程的使用权,如果JS执行时间过长,就会导致在下一帧开始时js没有及时归还主线程,导致下一帧动画没有按时渲染,可以通过requestAnimationFrame()来解决这个问题,这个方法会在每一帧被调用,通过API的回调,然后我们可以把JS运行任务分成一些更小的任务块(分到每一帧)在每一帧时间用完前,暂停JS执行,归还主线程,这样在下一帧开始时,主线程就可以按时执行布局和绘制,react fiber用到这个api做了很多优化,栅格化的整个流程时不占用主线程的,只在合成器线程和栅格线程中运行,这就意味着无需和JS抢占主线程,css中有个动画属性交transform,通过该属性实现的动画不会经过布局和绘制,而是直接运行在合成器线程和栅格线程。
梳理总结
浏览器进程中的网络线程请求到获取到html数据后,通过ipc将数据传给渲染器进程的主线程,主线程将html解析构造DOM树,然后进行样式计算,根据DOM树和生成好的样式生成layout tree,通过遍历layout tree生成绘制顺序表,接着遍历了layout tree生成了layertree,然后主线程将layertree和绘制顺序信息一起传给合成器线程,合成器线程按规则进行分图层,并把图层分为更小的图块(tiles)传给栅格化线程进行栅格化,栅格化完成后,合成器线程会获得栅格化线程传过来的“draw quads”图块信息,根据这些信息合成器线上合成了一个合成器帧,然后将合成器帧通过ipc传回给浏览器进程,浏览器进程再传GPU进行渲染,之后就展示到屏幕上了