跳到主要内容

浏览器进程和线程

腾讯的一道面试题:Chrome 浏览器是多进程还是单进程,是多线程还是单线程?

多进程和多线程​

多进程: 是指在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的,比如你可以听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。

多线程: 是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

浏览器多进程架构​

跟现在的很多多线程浏览器不一样,Chrome 浏览器使用多个进程来隔离不同的网页。因此在 Chrome 中打开一个网页相当于起了一个进程。

如图在 Chrome 的菜单/更多工具/任务管理工具下可以查看所有的进程:

Chrome 任务管理工具截图

那么 Chrome 为什么要使用多进程架构?​

在浏览器刚被设计出来的时候,那时的网页非常的简单,每个网页的资源占有率是非常低的,因此一个进程处理多个网页时可行的。

然后在今天,大量网页变得日益复杂。把所有网页都放进一个进程的浏览器面临在健壮性,响应速度,安全性方面的挑战。因为如果浏览器中的一个 tab 网页崩溃的话,将会导致其他被打开的网页应用。

另外相对于线程,进程之间是不共享资源和地址空间的,所以不会存在太多的安全问题,而由于多个线程共享着相同的地址空间和资源,所以会存在线程之间有可能会恶意修改或者获取非授权数据等复杂的安全问题。

在了解这个知识点,我们需要先说明下什么是浏览器内核。

浏览器主要包含哪些进程?​

  • Browser 进程:浏览器的主进程,只有一个。
    • 负责浏览器界面的显示与交互;
    • 各个页面的管理,创建和销毁其他进程;
    • 网络的资源管理、下载等。
  • Renderer 进程:也称为浏览器渲染进程或浏览器内核,内部是多线程的。主要负责页面渲染,脚本执行,事件处理等。
  • 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建。
  • GPU 进程:最多一个,用于 3D 绘制等。

进程间通信​

进程间使用 IPC (Inter Process Communication)进行通信。

浏览器内核(渲染进程)​

简单来说浏览器内核是通过取得页面内容、整理信息(应用 CSS)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。从上面我们可以知道,Chrome 浏览器为每个 tab 页面单独启用进程,因此每个 tab 网页都有由其独立的渲染引擎实例。

浏览器内核是多线程​

浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

  • GUI 渲染线程
  • JavaScript 引擎线程
  • 定时触发器线程
  • 事件触发线程
  • 异步 http 请求线程

GUI 渲染线程​

GUI 渲染线程负责渲染浏览器界面 HTML 元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。在 JavaScript 引擎运行脚本期间,GUI 渲染线程都是处于挂起状态的,也就是说被”冻结“了。

JavaScript 引擎线程​

JavaScript 引擎,也可以称为 JS 内核,主要负责处理 JavaScript 脚本程序,例如 V8 JavaScript 引擎线程理所当然是负责解析 JavaScript 脚本,运行代码。

JavaScript 是单线程的​

JavaScript 是单线程的, 那么为什么 JavaScript 要是单线程的?

这是因为 JavaScript 这门脚本语言诞生的使命所致:JavaScript 为处理页面中用户的交互,以及操作 DOM 树、CSS 样式树来给用户呈现一份动态而丰富的交互体验和服务器逻辑的交互处理。如果 JavaScript 是多线程的方式来操作这些 UI DOM,则可能出现 UI 操作的冲突;

如果 JavaScript 是多线程的话,在多线程的交互下,处于 UI 中的 DOM 节点就可能成为一个临界资源,假设存在两个线程同时操作一个 DOM,一个负责修改一个负责删除,那么这个时候就需要浏览器来裁决如何生效哪个线程的执行结果。当然我们可以通过锁来解决上面的问题。但为了避免因为引入了锁而带来更大的复杂性,JavaScript 在最初就选择了单线程执行。

GUI 渲染线程 与 JavaScript 引擎线程互斥!​

由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JavaScript 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。因此为了防止渲染出现不可预期的结果,浏览器设置 GUI 渲染线程与 JavaScript 引擎为互斥的关系,当 JavaScript 引擎执行时 GUI 线程会被挂起,GUI 更新会被保存在一个队列中等到引擎线程空闲时立即被执行。

JS 阻塞页面加载​

从上面我们可以推理出,由于 GUI 渲染线程与 JavaScript 执行线程是互斥的关系,当浏览器在执行 JavaScript 程序的时候,GUI 渲染线程会被保存在一个队列中,直到 JS 程序执行完成,才会接着执行。因此如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

定时触发器线程​

浏览器定时计数器(setTimeout 和 setInterval)并不是由 JavaScript 引擎计数的, 因为 JavaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时是更为合理的方案。

事件触发线程​

当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待 JS 引擎的处理。这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX 异步请求等,但由于 JS 的单线程关系所有这些事件都得排队等待 JS 引擎处理。

异步 http 请求线程​

在 XMLHttpRequest 在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript 引擎的处理队列中等待处理。

Browser 进程和 Renderer 进程的通信过程​

打开浏览器的一个 tab 页时,我们看下其中的大致过程:

  • Browser 进程收到用户请求,通过网络下载获取页面内容,然后将该任务通过 RendererHost 接口传递给 Renderer 进程;
  • Renderer 进程的 Renderer 接口收到消息,简单解释后,交给 GUI 渲染线程开始渲染;
  • GUI 渲染线程接收请求,加载网页并渲染网页,这个过程中可能需要 Browser 进程获取资源和 GPU 进程来帮助渲染,也可能会有 JS 引擎线程操作 DOM(可能造成回流并重绘);
  • 最后 Renderer 进程将结果传递给 Browser 进程;
  • Browser 进程接收到结果,并将结果绘制出来。

参考​