进程和线程
在理解浏览器架构之前,我们需要先掌握进程和线程的基础概念。
在现代操作系统中,进程和线程是程序执行的两个基本单位,构成CPU调度和资源管理的核心逻辑。
什么是进程
进程是操作系统分配资源的基本单位,可以把它想象成一个正在运行的程序实例,拥有独立的内存空间、资源系统、权限上下文等等。
-
每一个浏览器标签页、每一个打开的软件,通常都是一个进程。
-
不同进程之间彼此独立,一个崩溃不会直接影响另一个。
什么是线程
线程是CPU调度的最小单位,是进程中的“执行路径”。一个进程可以包含多个线程,这些线程共享进程的资源,但各自拥有自己的执行栈和上下文。
-
线程之间可以并发执行(前提是多核 CPU)
-
多个线程可共享内存,但也可能存在竞争和冲突问题
打个比方,一个进程呢就像是一家公司,拥有自己的办公楼(内存)和资源;而线程就是公司里的员工,共享一个资源池,但可以各自完成不同的任务
进程(Process) | 线程(Thread) | |
---|---|---|
定义 | 程序的独立运行单位 | 程序执行的最小单位 |
拥有资源 | 拥有独立内存空间、句柄等资源 | 共享进程资源 |
通信方式 | 复杂(如管道、IPC) | 简单(共享内存) |
崩溃影响 | 不影响其他进程 | 有可能影响同进程下其他线程 |
创建开销 | 大,需要完整上下文切换 | 小,调度灵活 |
应用场景 | 浏览器多标签、沙箱隔离 | 页面内 JS 执行、网络任务等 |
浏览器的多进程
为什么需要多进程
如果你使用 Chrome、Edge、Safari 等现代浏览器打开任务管理器,会发现即便只有一个标签页,也可能出现多个与该浏览器相关的进程。这不是 Bug,而是浏览器为提升性能、安全性和稳定性所做的一项核心设计:多进程架构
早期浏览器(如IE6)采用的是单进程架构,所有标签页、插件等等都跑在一个进程中。这种架构有如下致命缺陷:稳定性差、性能不佳、安全性低
第一点,稳定性差。早期浏览器只要有一个模块出错,整个进程就可能崩溃。
- 浏览器中的任意一个线程(如插件线程或渲染线程)抛出异常,都会直接导致整个进程终止。
- 早期浏览器为了实现丰富的多媒体体验,广泛依赖于第三方插件(如 Flash、Silverlight)。这些插件运行在浏览器进程中,一旦崩溃,整个浏览器随之关闭。
- 渲染模块也极不稳定。复杂的 JavaScript、DOM 操作可能导致浏览器引擎(如 Trident)崩溃,从而整个浏览器闪退。
就像一栋大楼只用了一个电闸,某层电器短路,全楼都跳闸断电。
第二点,在单进程浏览器中,所有页面的 JS 执行、UI 渲染、事件处理都运行在同一个线程中。这就带来了严重的性能瓶颈:JS 是单线程执行的,长时间的同步任务(如死循环)会阻塞整个渲染流程,页面 UI 的绘制与 JS 执行不能并发进行,例如下列代码:
```js
while (true) {
// 无限循环,占满主线程
}
```
会直接让页面卡死,鼠标点击无响应,甚至拖动都卡顿。更糟的是,所有标签页共享同一个线程,某个页面卡顿会拖垮整个浏览器的交互体验。此外,还有一个重要问题是内存回收不彻底:单进程架构下,多个标签页共享一个进程,关闭页面并不会立即释放其占用内存;长期运行下来,页面越多,内存占用越高,浏览器越发卡顿,导致“越用越慢”现象
第三点,单进程浏览器的安全性非常脆弱,主要体现在两个方面:
-
插件安全问题:
- 插件通常用 C/C++ 编写,运行时几乎不受限制;
- 插件运行在浏览器主进程中,若为恶意插件,可直接访问系统资源,读取文件、监听键盘,甚至植入病毒。
-
脚本攻击问题:
- 页面中的 JS 代码可能通过浏览器漏洞突破沙箱限制,窃取 Cookie、劫持 DOM,甚至进一步攻击操作系统;
- 所有页面共享一个运行环境,使得一个页面的攻击可能影响所有标签页及用户数据。
单进程架构缺乏必要的权限隔离机制,一旦被攻破,浏览器就如同“门户洞开”。
Chrome 引入的多进程架构
自 Chrome 起,浏览器引入多进程模型,将不同功能职责划分到不同的系统进程中。
问题类型 | 单进程架构的弊端 | 多进程架构的解决方式 |
---|---|---|
不稳定 | 某个页面崩溃,整个浏览器进程直接退出,所有标签页关闭 | 每个页面/插件运行在独立进程,崩溃互不影响 |
不流畅 | 某个页面 JS 死循环,主线程被阻塞,其他页面卡顿 | 每个标签页独立渲染进程,互不干扰 |
内存泄漏 | 单个页面内存泄漏,长期存在造成整个浏览器臃肿 | 页面关闭后对应进程被销毁,系统自动回收资源 |
安全问题 | 所有页面共享内存空间,容易被恶意代码入侵 | 渲染进程运行在沙箱中,权限受限,增强安全性 |
浏览器的常见进程包括:1个浏览器主进程,1个GPU进程,1个网络进程,多个渲染进程,和多个插件进程
-
浏览器进程: 负责控制浏览器除标签页外的界面,包括地址栏、书签、前进后退按钮等,以及负责与其他进程的协调工作,同时提供存储功能
-
GPU进程:负责整个浏览器界面的渲染。Chrome刚开始发布的时候是没有GPU进程的,而使用GPU的初衷是为了实现3D CSS效果,只是后面网页、Chrome的UI界面都用GPU来绘制,这使GPU成为浏览器普遍的需求,最后Chrome在多进程架构上也引入了GPU进程
-
网络进程:负责发起和接受网络请求,以前是作为模块运行在浏览器进程一时在面的,后面才独立出来,成为一个单独的进程
-
插件进程:主要是负责插件的运行,因为插件可能崩溃,所以需要通过插件进程来隔离,以保证插件崩溃也不会对浏览器和页面造成影响
-
渲染进程:负责控制显示tab标签页内的所有内容,核心任务是将HTML、CSS、JS转为用户可以与之交互的网页,排版引擎Blink和JS引擎V8都是运行在该进程中,默认情况下Chrome会为每个Tab标签页创建一个渲染进程
浏览器的多线程
我们平时看到的浏览器呈现页面过程中,大部分工作都是在渲染进程中完成,主要有以下几个线程:GUI渲染线程、JS引擎线程、事件触发线程、计时器线程和异步http请求线程
-
GUI渲染线程:负责渲染页面,解析html和CSS、构建DOM树、CSSOM树、渲染树、和绘制页面,重绘重排也是在该线程执行
-
JS引擎线程:一个tab页中只有一个JS引擎线程(单线程),负责解析和执行JS。GUI渲染进程不能同时执行,只能一个一个来,如果JS执行过长就会导致阻塞掉帧
-
计时器线程:指setInterval和setTimeout,因为JS引擎是单线程的,所以如果处于阻塞状态,那么计时器就会不准了,所以需要单独的线程来负责计时器工作
-
异步http请求线程: XMLHttpRequest连接后浏览器开的一个线程,比如请求有回调函数,异步线程就会将回调函数加入事件队列,等JS引擎空闲执行
-
事件触发线程:主要用来控制事件循环,比如JS执行遇到计时器,AJAX异步请求等,就会将对应任务添加到事件触发线程中,在对应事件符合触发条件触发时,就把事件添加到待处理队列的队尾,等JS引擎处理