# 了解一下进程和线程

# 含义

进程是 CPU 资源分配的最小单位

线程是 CPU 调度的最小单位

# 关系

一个进程由一个或者多个线程组成,线程是一个进程中代码的不同执行路线

一个进程的内存空间是共享的,每个线程都可以共享这些内存

# 多进程和多线程

# 含义

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

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

(eg.以 Chrome 浏览器为例,当打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程,比如渲染线程,JS 引擎线程,HTTP 请求线程等等。当发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。)

# 浏览器内核(渲染引擎)

# 含义

简单来说浏览器内核是通过取得页面内容、整理信息(应用 CSS)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。

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

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

# GUI 渲染线程

  • 主要负责页面的渲染,解析 HTML、CSS、构建 DOM 树,布局和绘制等。
  • 当界面需要重绘或者由于某种操作引发回流时,将执行该线程
  • 该线程与 JS 引擎线程互斥、当执行 JS 引擎线程时,GUI 渲染会被挂起,当任务队列空闲时,JS 引擎才会去执行 GUI 渲染

# JS 引擎线程

  • 该线程主要负责处理 JavaScript 脚本,执行代码
  • 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS 引擎线程的执行
  • 当然,该线程与 GUI 渲染线程互斥,当 JS 引擎线程执行 JavaScript 脚本时间过长,将导致页面渲染的阻塞

# 定时器触发线程

  • 负责执行异步定时器一类的函数的线程,如:setTimeout,setInterval
  • 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计时完毕后,事件触发线程会将计时完毕后的事件加入到任务队列的尾部,等待 JS 引擎线程的执行

# 事件触发线程

  • 主要负责将准备好的事件交给 JS 引擎线程执行
  • 比如 setTimout 定时器计时结束,ajax 等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS 引擎线程的执行

# 异步 http 请求线程

  • 负责执行异步请求一类的函数的线程,如:Promise,axios,ajax 等
  • 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待 JS 引擎线程的执行

# 单进程浏览器时代

浏览器进程架构的演化 (opens new window)

顾名思义,单进程浏览器是指浏览器所以模块都运行再同一个进程里,这些模块包含了网络、插件、JavaScript 运行环境、渲染引擎和页面等。

单进程浏览器的架构如下图所示 👇

image-20211112102405845

思考题:要是面试官要你详细说的话,你该怎么去表达清楚?

如此多的功能模块运行在一个进程里,肯定有着不足的:

  • 不稳定性

    页面渲染引擎出错,或者 JavaScript 执行出错,或者某个插件奔溃等等,无论这些程序是在进程中的哪个线程运行,都会导致整个进程挂掉

  • 不流畅

    由于 CPU 只能在某个时间点处理某个进程中的某一条线程,而一个页面线程既包括页面渲染,页面展现,还包括 JS 脚本的执行以及各种插件的运行,如果 JS 代码中有一个死循环,那么浏览器就卡住了,就算没有死循环,我们的 JS 或者插件也需要一直运行一些东西,如果 JS 正在执行动画,而 CPU 被插件程序抢过去执行其他任务了,这时候动画就卡住了

  • 不安全

    进程中的所有线程是共享进程的内存空间的,而插件运行就是在其中一个线程中,倘若有恶意插件在运行,由于线程之间是共享进程资源的,恶意插件的权限是跟浏览器同等级的,它能随意获取到其他线程的信息,例如表单信息,

# 多进程浏览器时代

基于以上的问题,现代浏览器已经解决了这些问题了,是如何解决的呢?那我们聊一聊多进程时代

# 早期多进程架构

image-20211112102413913

从图中可以看出,Chrome 的页面是运行在单独的渲染进程中的,同时页面里的插件也是运行在单独的插件进程之中,而进程之间是通过 IPC 机制进行通信(如图中虚线部分)

**我们先看看如何解决不稳定的问题:**由于进程是相互隔离的,所以当一个页面或者插件崩溃时,影响到的仅仅是当前的页面进程或者插件进程,并不会影响到浏览器和其他页面,这就完美地解决了页面或者插件的崩溃会导致整个浏览器崩溃,也就是不稳定的问题。

**接下来再来看看不流畅的问题是如何解决的:**同样,JavaScript 也是运行在渲染进程中的,所以即使 JavaScript 阻塞了渲染进程,影响到的也只是当前的渲染页面,而并不会影响浏览器和其他页面,因为其他页面的脚本是运行在它们自己的渲染进程中的。所以当我们再在 Chrome 中运行上面那个死循环的脚本时,没有响应的仅仅是当前的页面。

对于内存泄漏的解决方法那就更简单了,因为当关闭一个页面时,整个渲染进程也会被关闭,之后该进程所占用的内存都会被系统回收,这样就轻松解决了浏览器页面的内存泄漏问题。

最后我们再来看看上面的两个安全问题是怎么解决的: 用多进程架构的额外好处是可以使用安全沙箱,你可以把沙箱看成是操作系统给进程上了一把锁,沙箱里面的程序可以运行,但是不能在你的硬盘上写入任何数据,也不能在敏感位置读取任何数据,例如你的文档和桌面。Chrome 把插件进程和渲染进程锁在沙箱里面,这样即使在渲染进程或者插件进程里面执行了恶意程序,恶意程序也无法突破沙箱去获取系统权限。


下面的才是我们的重点:目前的 Chrome 架构就是采用下面的方案,对于后面常见的面试题:从浏览器输入 URL 按回车到页面显示都发生了什么 这个经典面试题而言,有一个系统的知识体系,比背诵条例而言,更为重要!


# 目前多进程架构

Chrome 发展肯定是有新的变化的,我们先看看最新的 Chrome 进程架构,可以参考这个图 👇

image-20211112102421890

从图中可以看出,最新的 Chrome 浏览器包括:1 个浏览器(Browser)主进程、1 个 GPU 进程、1 个网络(NetWork)进程、多个渲染进程和多个插件进程。

下面我们来逐个分析下这几个进程的功能 👇

# 浏览器进程

主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。

# 渲染进程

核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。

# GPU 进程

其实,Chrome 刚开始发布的时候是没有 GPU 进程的。而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。

# 网络进程

主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。

# 插件进程

每个类型的插件对应一个进程,仅当使用该插件时才创建。

主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。

不过凡事都有两面性,虽然多进程模型提升了浏览器的稳定性、流畅性和安全性,但同样不可避免地带来了一些问题:

  • 更高的资源占用因为每个进程都会包含公共基础结构的副本(如 JavaScript 运行环境),这就意味着浏览器会消耗更多的内存资源。
  • 更复杂的体系架构浏览器各模块之间耦合性高、扩展性差等问题,会导致现在的架构已经很难适应新的需求了

# 未来面向服务的架构

为了解决这些问题,在 2016 年,Chrome 官方团队使用“面向服务的架构”(Services Oriented Architecture,简称 SOA)的思想设计了新的 Chrome 架构

Chrome 最终要把 UI、数据库、文件、设备、网络等模块重构为基础服务,类似操作系统底层服务,下面是 Chrome“面向服务的架构”的进程模型图:

image-20211112102433729


# 涉及面试题

# 为什么单进程浏览器当时不可以采用安全沙箱?

如果一个进程使用了安全沙箱之后,该进程对于操作系统的权限就会受到限制,比如不能对一些位置的文件进行读写操作,而这些权限浏览器主进程所需要的,所以安全沙箱是不能应用到浏览器主进程之上的。


# 打开 Chrome 浏览器一个 Tab 页面,至少会出现几个进程?

最新的 Chrome 浏览器包括至少四个:1 个浏览器(Browser)主进程、1 个 GPU 进程、1 个网络(NetWork)进程、多个渲染进程和多个插件进程,当然还有复杂的情况; 1. 页面中有 iframe 的话,iframe 会单独在进程中 2. 有插件的话,插件也会开启进程 3. 多个页面属于同一站点,并且从 a 打开 b 页面,会共用一个渲染进程 4. 装了扩展的话,扩展也会占用进程 这些进程都可以通过 Chrome 任务管理器来查看


# 即使如今多进程架构,还是会碰到单页面卡死的最终崩溃导致所有页面崩溃的情况,讲一讲你的理解?

Chrome 的默认策略是,每个标签对应一个渲染进程。但是如果从一个页面打开了新页面,而新页面和当前页面属于同一站点时,那么新页面会复用父页面的渲染进程。官方把这个默认策略叫 process-per-site-instance。更加简单的来说,就是如果多个页面符合同一站点,这几个页面会分配到一个渲染进程中去,所以有这样子的一种情况,一个页面崩溃了,会导致同一个站点的其他页面也奔溃,这是因为它们使用的是同一个渲染进程。有人会问为什么会跑到一个进程里面呢? 你想一想呀,属于同一家的站点,比如下面三个:

https://time.geekbang.org

https://www.geekbang.org

https://www.geekbang.org:8080

它们在一个渲染进程中的话,它们就会共享 JS 执行环境,也就是 A 页面可以直接在 B 页面中执行脚本了,有些时候就是有这样子的需求嘛。

# 为什么 Javascript 是单线程的呢?

这是因为 Javascript 这门脚本语言诞生的使命所致:JavaScript 为处理页面中用户的交互,以及操作 DOM 树、CSS 样式树来给用户呈现一份动态而丰富的交互体验和服务器逻辑的交互处理。如果 JavaScript 是多线程的方式来操作这些 UI DOM,则可能出现 UI 操作的冲突; 如果 Javascript 是多线程的话,在多线程的交互下,处于 UI 中的 DOM 节点就可能成为一个临界资源,假设存在两个线程同时操作一个 DOM,一个负责修改一个负责删除,那么这个时候就需要浏览器来裁决如何生效哪个线程的执行结果。当然我们可以通过锁来解决上面的问题。但为了避免因为引入了锁而带来更大的复杂性,Javascript 在最初就选择了单线程执行。

# 总结

  • 早期浏览器:不稳定(单独进程) 不流畅(单独进程) 不安全(沙箱)
  • 早期多进程浏览器: 主进程 渲染进程 插件进程
  • 现代多进程架构: 主进程 渲染进程 插件进程 GPU 进程 网络进程
  • 未来面向服务架构

# 参考

浏览器与 Node 的事件循环(Event Loop)有何区别? (opens new window)

浏览器进程?线程?傻傻分不清楚! (opens new window)

你不知道的 Web Workers (上)[7.8K 字 | 多图预警] (opens new window)

「浏览器工作原理」写给女友的秘籍-浏览器组成&网络请求篇(1.2W 字) (opens new window)

浏览器进程架构的演化 (opens new window)

从浏览器多进程到 JS 单线程,JS 运行机制最全面的一次梳理 (opens new window)

最近更新: 4 小时前