1. 进程和线程
用chrome打开一个页面,chrome开启了五个进程。
注意点:
-
什么是进程?
一个进程就是一个程序运行实例。启动一个程序后,操作系统会为该进程新开辟一块儿内存,用于存放代码、运行中的数据、和一个执行任务的主线程,这样的一个运行环境叫做进程。
-
线程和进程的关系?
- 线程不能单独存在,由进程来启动和管理
- 线程之间共享进程中的数据(可以对公共数据进行读写)
- 进程中的任意一线程执行出错,都会导致整个进程的崩溃
- 一个进程关闭之后,操作系统会回收进程所占用的内存(即使内存泄漏,那部分内存也会被回收)
- 进程之间的内容相互隔离(比如QQ和微信,通信使用进程间通信机制IPC)
-
为什么会开启5个进程?
- 查看后续多进程浏览器结构
2. 单进程浏览器时代
2007年之前,市面上的浏览器都是单进程的,这意味着浏览器所有的功能模块都是运行在同一个进程里
单进程浏览器的架构:
2.1 单进程浏览器存在的问题
-
不稳定
早期浏览器许多功能借助插件实现(web视频、web游戏等),插件最容易出问题。
除了插件之外,渲染引擎模块也不稳定,一些复杂的JS代码可能导致模块崩溃。
二者都运行于浏览器的页面线程中,任何一个出现问题都会导致该线程崩溃进而导致整个浏览器进程崩溃。
-
不流畅
以页面线程为例,js脚本、页面渲染、插件都运行在该线程中,如果一个页面的脚本出现了死循环,这就会导致其他的页面和插件没有机会执行任务,整个浏览器失去响应,变卡顿。具体详情请查看页面的事件循环系统
-
不安全
插件可以使用 C/C++ 等代码编写,通过插件可以获取到操作系统的任意资源。
页面脚本也有可能通过浏览器的漏洞获取系统权限。
3. 多进程浏览器时代
3.1 早期多进程架构
早期chrome多进程架构:
页面单独运行在渲染进程中,一个页面/插件对应一个渲染/插件进程(关于“同一站点“问题看文末补充)
3.2 早期多进程架构解决的问题
-
不稳定
插件、渲染进程和浏览器主进程相互隔离,一旦插件或者页面崩溃,只会影响当前页面或者插件所在进程,而不会影响到浏览器其他页面所在的进程。
-
不流畅
进程相互隔离,js循环阻塞只会让当前页面失去响应。同时如果出现内存泄漏,泄漏的内存也会随着当前页面的关闭(当前页面渲染进程结束)一并回收。
-
不安全
多进程架构使用安全沙箱(看成是操作系统给进程上了一把锁),沙箱里面的程序可以运行,但是不能在你的硬盘上写入任何数据,也不能在敏感位置读取任何数据。
3.3 目前多进程架构
相当于把一些原本在浏览器主进程里运行的模块独立出来
各进程功能:
- 浏览器进程。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能
- 渲染进程。核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程(”同一页面“情况存在差异,详见文末)。出于安全考虑,渲染进程都是运行在沙箱模式下。
- GPU 进程。初衷是为了实现 3D CSS 的效果,后来网页、Chrome 的 UI 界面都选择采用 GPU 来绘制
- 网络进程。主要负责页面的网络资源加载
- 插件进程。主要是负责插件的运行
3.4 目前多进程模型带来的问题
-
更高的资源占用
因为每个进程都会包含公共基础结构的副本(如 JavaScript 运行环境)
-
更复杂的体系架构
浏览器各模块之间耦合性高、扩展性差
3.5 未来面向服务的架构(SOA)
为解决上述问题,Chrome 整体架构会朝向现代操作系统所采用的“面向服务的架构” 方向发展,原来的各种模块会被重构成独立的服务(Service)(开头的五个进程的截图),每个服务都可以在独立的进程中运行,访问服务(Service)必须使用定义好的接口,通过 IPC 来通信,从而构建一个更内聚、松耦合、易于维护和扩展的系统。
Chrome“面向服务的架构”进程模型图:
Chrome 最终要把 UI、数据库、文件、设备、网络等模块重构为基础服务,类似操作系统底层服务。
同时 Chrome 还提供灵活的弹性架构,在强大性能设备上会以多进程的方式运行基础服务,但是如果在资源受限的设备上,Chrome 会将很多服务整合到一个进程中,从而节省内存占用。
3.6 一些常见问题
-
即使是如今的多进程架构,用户偶尔还会碰到一些由于单个页面卡死最终崩溃导致所有页面崩溃的情况,这是什么原因呢?
“同一站点”定义:根域名(例如,geekbang.org)+协议(例如,https:// 或者http://),还包含了该根域名下的所有子域名和不同的端口,比如下面这三个:
都是属于同一站点,因为它们的协议都是https,而根域名也都是geekbang.org。你也许了解同源策略,但是同一站点和同源策略还是存在一些不同地方,在这里你需要了解它们不是同一件事就行了。
Chrome的默认策略是,每个标签对应一个渲染进程。但是如果从一个页面打开了新页面,而新页面和当前页面属于同一站点时,那么新页面会复用父页面的渲染进程。官方把这个默认策略叫
process-per-site-instance
直白的讲,就是如果几个页面符合同一站点,那么它们将被分配到一个渲染进程里面去。 所以,这种情况下,一个页面崩溃了,会导致同一站点的页面同时崩溃,因为他们使用了同一个渲染进程。
为什么要让他们跑在一个进程里面呢?
因为在一个渲染进程里面,他们就会共享JS的执行环境,也就是说A页面可以直接在B页面中执行脚本。因为是同一家的站点,所以是有这个需求的
-
单进程浏览器开多个页面,渲染线程也只有一个吗?一个页面开一个线程更合理?
IE6时代,浏览器是单进程的,所有页面也都是运行在一个主线程中的,当时IE6就是这样设计,而且此时的IE6是单标签,也就是说一个页面一个窗口。
这时候,国内有很多国产浏览器,都是基于IE6来二次开发的,而IE6原生架构就是所有页面跑在单线程里面的,意味着,所有的页面都共享着同一套JavaScript运行环境,同样,对于存储Cookie 也都是在一个线程里面操作的。
而且这些国产浏览器由于需要,都采用多标签的形式,所以其中的一个标签页面的卡顿都会影响到整个浏览器。
基于卡顿的原因,国内浏览器就开始尝试支持页面多线程,也就是让部分页面运行在单独的线程之中,运行在单独的线程之中,意味着每个线程拥有单独的JavaScript执行环境,和Cookie环境,这时候问题就来了:
比如A站点页面登陆一个网站,保存了一些Cookie数据到磁盘上,再在当前线程环境中保存部分Session数据,由于Session是不需要保存到硬盘上的,所以Session只会保存在当前的线程环境中。这时候再打开另外一个A站点的页面,假设这个页面在另外一个线程中里面,那么它首先读取硬盘上的Cookie信息,但是,由于Session信息是保存在另外一个线程里面的,无法直接读取,这样就要实现一个Session同步的问题,由于IE并没有源代码,所以实现起来非常空难,国内浏览器花了好长一点时间才解决这个问题的。
Session问题解决了,但是假死的问题依然有,因为进程内使用了一个窗口,这个窗口是依附到浏览器主窗口之上的,所以他们公用一套消息循环机制,消息循环我们后面会详细地讲,这也就意味这一个窗口如果卡死了。也会导致整个浏览器的卡死。国产浏览器又出了一招,就是把页面做成一个单独的弹窗,如果这个页面卡死了,就把这个弹窗给隐藏掉。
这里还要提一下为什么Chrome中的一个页面假死不会影响到主窗口呢?
这是因为chrome输出的实际上图片,然后浏览器端把图片贴到自己的窗口上去,在Chrome的渲染进程内,并没有一个渲染窗口,输出的只是图片,如果卡住了,顶多图片不更新了。
国产浏览器这一套技术花了四五年时间,等这套技术差不多成熟时,Chrome发布了 :(