本文主要是梳理在现代浏览器进程模型的学习过程中的一些收获,如有疑问,欢迎补充指正。参考的相关文章和链接已放在文章末尾。
进程和线程的区别
进程可以看成正在被执行的应用程序(executing program)。而线程是跑在进程里面的,一个进程里面可能有一个或者多个线程,这些线程可以执行任何一部分应用程序的代码。关于进程和线程的区别,有一个非常形象的比喻:
- 进程是一个工厂,工厂有它的独立资源
- 工厂之间相互独立
- 线程是工厂中的工人,多个工人协作完成任务
- 工厂内有一个或多个工人
- 工人之间共享空间
进一步完善一下
- 工厂的资源 -> 系统分配的内存(独立的一块内存)
- 工厂之间的相互独立 -> 进程之间相互独立
- 多个工人协作完成任务 -> 多个线程在进程中协作完成任务
- 工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成
- 工人之间共享空间 -> 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)
补充一下较为官方的解释:
- 进程是 CPU 资源分配的最小单位(能拥有资源和独立运行的最小单位)
- 进程是 CPU 调度的最小单位,线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程
有时候为了满足功能的需要,创建的进程会叫系统创建另外一些进程去处理其它任务,不过新建的进程会拥有全新的独立的内存空间而不是和原来的进程共用内存空间。如果这些进程需要通信,它们要通过IPC机制(Inter Process Communication)来进行。很多应用程序都会采取这种多进程的方式来工作,因为进程和进程之间是互相独立的它们互不影响,换句话来说,如果其中一个工作进程(worker process)挂掉了其他进程不会受到影响,而且挂掉的进程还可以重启。
浏览器是一个多进程多线程的应用程序
浏览器内部的工作极其复杂(堪比操作系统),为了减少连环崩溃的概率,当启动浏览器后,它会自动启动多个进程
可以在浏览器的任务管理器中查看当前的所有进程
其中,最主要的进程有:
- 浏览器进程(主进程):只有一个,负责界面显示、用户交互、子进程的管理等。进程内部会启动多个线程处理不同的任务。
- 渲染进程:渲染进程启动后,会开启一个渲染主线程,主线程负责执行 HTML、CSS、JS代码。
- 网络进程:负责加载网络资源。网络进程内部会启动多个线程来处理不同的网络任务。
- GPU进程:最多一个,用于3D绘制等
- 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
网络 之前是作为一个线程运行在浏览器进程里面的,后来成为一个单独的进程,网络资源的管理,下载等
采用多进程架构的好处
- 多进程可以使浏览器具有很好的容错性。对于大多数简单的情景来说,Chrome会为每个tab单独分配一个属于它们的渲染进程(render process)。举个例子,假如你有三个tab,你就会有三个独立的渲染进程。当其中一个tab的崩溃时,你可以随时关闭这个tab并且其他tab不受到影响。可是如果所有的tab都跑在同一个进程的话,它们就会有连带关系,一个挂全部挂。
- 可以提供安全性和沙盒性(sanboxing)。因为操作系统可以提供方法让你限制每个进程拥有的能力,所以浏览器可以让某些进程不具备某些特定的功能。例如,由于tab渲染进程可能会处理来自用户的随机输入,所以Chrome限制了它们对系统文件随机读写的能力。
不过多进程架构也有它不好的地方,那就是进程的内存消耗。由于每个进程都有各自独立的内存空间,所以它们不能像存在于同一个进程的线程那样共用内存空间,这就造成了一些基础的架构(例如V8 JavaScript引擎)会在不同进程的内存空间同时存在的问题,这些重复的内容会消耗更多的内存。所以为了节省内存,Chrome会限制被启动的进程数目,当进程数达到一定的界限后,Chrome会将访问同一个网站的tab都放在一个进程里面跑。chrome官方说明
渲染进程
渲染进程负责标签(tab)内发生的所有事情。在渲染进程里面,主线程(main thread)处理了绝大多数你发送给用户的代码。如果你使用了web worker或者service worker,相关的代码将会有工作线程(worker thread)处理。合成(compositor)以及光栅(raster)线程运行在渲染进程里面用来高效流畅地渲染出页面。
渲染进程的主要任务是将HTML, CSS,以及JavaScript转变为我们可以进程交互的网页内容, 它处理的任务包括但不限于:
- 解析HTML
- 解析CSS
- 计算样式
- 布局
- 处理图层
- 每秒把页面画 60 次
- 执行全局 JS 代码
- 执行事件处理函数
- 执行计数器的回调函数
- ......
chrome浏览器的进程模式
为了节省内存,Chrome提供了四种进程模式(Process Models),不同的进程模式对tab进程做不同的处理。
- Process-per-site-instance(default): 同一个 site-instance 使用一个进程
- Process-per-sit: 同一个 site 使用一个进程
- Process-per-tab: 每一个 tab 使用一个进程
- Single Process: 所有 tab 共用一个进程 这里给出 site 和 site-instance 的定义
- site 指的是相同的域名和协议(与同源策略不同,同源策略还涉及到子域名和端口)
- site instance 指满足下面两种情况并且打开的新页面和旧页面属于上面定义的同一个 site-instance
- 用户通过
<a target="_blank">这种方式点击打开的新页面 - JS代码打开的新页面(比如:
window.open)
- 用户通过
Process-per-site-instance 是最重要的,因为这个是Chrome默认使用的模式,也就是几乎所有的用户都在用的模式。当你打开一个 tab 访问 a.baidu.com,然后再打开一个 tab 访问 b.baidu.com,这两个 tab 会使用 两个进程。而如果你再 a.baidu.com 中,通过 JS 代码打开了 b.baidu.com 页面,这两个 tab 会使用同一个进程
Process-per-site-instance兼容了性能与易用性,是一个比较中庸通用的模式
- 相较于 Process-per-tab,能够少开很多进程,就意味着更少的内存占用
- 相较于 Process-per-site,能够更好的隔离相同域名下毫无关联的 tab,更加安全
导航的时候都发生了什么
浏览器进程运行
多进程架构启动多个进程处理不同的任务。选项卡外部的所有内容都由浏览器进程处理。在地址栏输入URL时,由浏览器进程的UI线程处理
处理输入
当用户开始在地址栏输入时,UI线程需要判断是搜索查询还是URL
- 搜索查询:发送到搜索引擎
- URL:请求URL的网站
开始导航
- UI 线程启动网络调用以获取站点内容,选项卡加载转圈
- 通过 IPC 机制通知网络进程通过 DNS 查找域名对应 IP 及建立 http 连接
读取响应结果
确定文件类型,获取响应文件是 HTML,则将数据传递给渲染进程。如果为.zip或者其他文件则将数据传递给下载股那里其
查找渲染进程
网路进程告知UI线程数据已准备就绪,UI线程找到渲染进程以继续渲染网页。
由于网络请求可能需要几百毫秒才能得到响应,为加速此过程,在开始导航网络线程发送url请求时,已经主动进行查找、启动渲染进程,数据接收完成后,渲染进程已备用
提交导航
现在数据和渲染器进程已准备就绪,IPC将从浏览器进程发送到渲染进程以提交导航。渲染进程确认提交完成,导航完成。文档加载开始。
- UI更新:地址栏更新、安全指示器、站点设置UI会反映新页面站点信息
- 选项卡的会话历史记录更新(前进/后退),为便于关闭浏览器后恢复,历史记录到磁盘
初始化 load complete
提交导航后,渲染器进程将继续加载资源并呈现页面,一旦渲染器进程“完成”(onload事件在所有帧上触发执行完成后)渲染,它就会将IPC发送回浏览器进程, UI线程停止选项卡的加载转圈。