技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTBR

深入理解浏览器中的进程与线程

所属馆藏
Browser
文件格式
Markdown
原始路径
Browser/36-深入理解浏览器中的进程与线程
本文目录12 个章节
  1. 一、进程与线程
  2. 二、浏览器为什么采用多进程
  3. 三、Chrome/Chromium 中有哪些进程
  4. 四、渲染进程中的线程和执行环境
  5. 五、浏览器进程模型名词的历史背景
  6. 六、进程间通信(IPC)
  7. 七、用户线程与内核线程模型(历史背景)
  8. 八、多标签页之间如何通信
  9. 九、孤儿进程和僵尸进程
  10. 十、协程和用户态调度
  11. 十一、总结
  12. 参考资料

深入理解浏览器中的进程与线程

Category(分类): Browser Status: 已更新

进程、线程和协程是理解浏览器稳定性、渲染性能、Worker、网络请求和跨标签页通信的基础。本文保留原文中操作系统概念、Chrome 进程、渲染线程、IPC、僵尸/孤儿进程和协程等内容,同时修正“每个标签页固定一个进程”“计时器有独立 JS 线程”等过于绝对的说法。

一、进程与线程

1. 进程

启动程序时,操作系统会创建一个进程,为它提供虚拟地址空间、权限上下文、打开的文件和其他资源。进程内包含程序代码、运行数据以及一个或多个线程。进程结束后,操作系统会回收或关闭它持有的资源,但共享资源、文件写入和外部服务还可能有自己的生命周期。

进程是重要的隔离和资源管理边界,但“进程之间完全隔离”是教学简化。进程可以通过共享内存、文件、管道、套接字、消息队列等方式通信,操作系统内核和驱动也会参与多个进程的协作。

2. 线程

线程是进程中的执行流。线程共享进程的代码段、堆和许多进程级资源,但每个线程有自己的栈、寄存器、程序计数器和调度状态。

进程与线程示意

同一进程中的线程共享数据很方便,但也带来竞态、死锁和内存可见性问题。一个线程的未处理原生异常可能使整个进程崩溃;普通 JavaScript 异常通常由当前执行环境处理,但未捕获错误、Worker 错误或引擎 bug 仍可能导致 Worker 或渲染进程终止。

3. 并发与并行

  • 并发:多个任务在时间上交替推进;
  • 并行:多个任务在同一时刻使用不同的 CPU 核心或执行资源。

单核处理器可以通过抢占式时间片实现并发,多核处理器才有机会让多个线程真正并行。线程数量超过可运行核心数时,仍然可以并发,但需要调度和上下文切换。

任务调度和时间片

操作系统调度器会在可运行线程之间切换,但具体调度算法、时间片和优先级由操作系统、平台和任务类型决定,不应把“所有系统都严格按固定时间片轮转”当成精确规则。

二、浏览器为什么采用多进程

早期浏览器常把网络、渲染、脚本、插件和多个页面放在一个进程中。一个页面或插件卡死,可能拖住整个浏览器;不同页面共享同一地址空间也扩大了安全风险。

多进程架构主要改善三件事:

  1. 稳定性:页面或服务崩溃时,浏览器有机会隔离、重启或关闭受影响部分;
  2. 安全性:渲染器可以运行在沙盒和受限权限下,减少网页直接接触本地资源的机会;
  3. 性能隔离:不同站点、GPU、媒体和网络任务可以分开调度。

代价是进程启动、IPC、内存复制和服务管理都有成本,所以浏览器会根据设备内存、页面关系和压力动态调整。

三、Chrome/Chromium 中有哪些进程

1. 没有固定的“至少四个进程”

旧文章常说打开一个页面至少有“浏览器、GPU、网络、渲染”四个进程。这个数字只适合描述某个时期、某个平台和某种配置,不能作为现代 Chrome 的固定事实。实际进程还可能包括 Viz、实用程序、音视频解码、扩展、崩溃处理和其他服务;某些服务也可能合并在其他进程中。

现代 Chromium 通常可以观察到:

  • Browser process:浏览器 UI、导航、权限、会话以及协调工作;
  • Renderer process:运行 Blink、V8,处理特定站点和框架的网页内容;
  • GPU/Viz process:处理 GPU 资源、栅格化和跨进程合成聚合;
  • Utility process:按需承载网络、媒体、数据解码、打印等服务;
  • Extension/plugin process:扩展和兼容插件的隔离环境。

进程列表会因操作系统、Chrome 版本、WebView、无头模式、GPU 黑名单、站点隔离和设备内存而变化。可在 Chrome 任务管理器、操作系统任务管理器或 DevTools 中观察当前实例,不要根据一次观察推导通用规范。

2. 渲染进程的归属

“每个标签页一个渲染进程”是早期易懂的模型。现代 Chromium 更关注 site/site instance 和站点隔离:

  • 不同站点通常使用不同渲染进程;
  • 跨站 iframe 可能在独立渲染进程中运行;
  • 同一站点的多个标签页可能共享,也可能因关系、内存压力和策略而分开;
  • 同源并不等于一定同进程,同站点也不等于一定同进程;
  • Android WebView 等嵌入环境的进程模型可能与桌面 Chromium 不同。

3. 各类进程的工作边界

进程/服务主要职责注意事项
浏览器进程地址栏、标签页 UI、导航、权限和进程协调不是所有网络/存储实现都必须在同一线程
渲染进程Blink、V8、DOM、CSS、页面脚本和事件进程归属由站点隔离和资源策略决定
GPU/Viz图形资源、栅格化、聚合和显示GPU 加速失败时可能回退 CPU
网络服务DNS、连接、HTTP、缓存和安全检查可以是独立服务,也会随平台配置变化
实用程序进程媒体、解码、打印等特定能力按需启动,不是每次导航都存在
扩展/插件扩展和兼容插件代码Flash 等旧插件不代表现代网页通用路径

四、渲染进程中的线程和执行环境

现代 Chromium RenderingNG 的线程结构比“GUI 线程、JS 线程、计时器线程、HTTP 线程、事件线程”这张固定清单更复杂。常见角色包括:

1. 主线程

主线程通常负责:

  • HTML、CSS 和其他文档格式解析;
  • JavaScript 执行、DOM API 和事件分发;
  • 样式计算、布局、绘制生命周期;
  • 命中测试、部分输入处理和页面任务调度。

同一个渲染进程中的多个 frame 可能共享主线程,因此一个 frame 的长任务可能影响同进程的其他内容。

2. 合成线程和栅格工作线程

合成线程负责输入路由、滚动、合成动画和图层组织;栅格任务通常由合成侧协调并交给一个或多个工作线程、Viz 或 GPU 执行。线程数量随设备和内容变化。

历史上的单进程模型

合成线程不是“所有动画都不经过主线程”的保证。只有浏览器能处理的合成属性和输入路径才可能绕过部分主线程工作;内容变化、布局、绘制、事件监听和长任务仍可能让动画卡顿。

3. Worker 执行环境

Dedicated Worker、SharedWorker 和 Service Worker 都是独立的 JavaScript 执行环境,拥有各自的事件循环。它们不能直接访问页面 DOM,通常使用 postMessage() 和结构化克隆传递数据。

Worker 不保证对应一个独立的操作系统进程。浏览器可以用线程、进程或其他调度方式实现它,开发者只能依赖 Web API 的隔离和通信语义。

4. 计时器、网络和事件不是固定的专用 JS 线程

setTimeout()setInterval()、Fetch、XHR、WebSocket 和用户事件由浏览器的底层服务和调度器负责等待或接收,但回调最终会作为任务进入相应 JavaScript 执行环境的事件循环。它们不是“每个 API 各有一个可供 JS 直接使用的线程”。

计时器只能表示“最早在指定延迟后排队”,不能保证精确时间执行:

setTimeout(() => {
  console.log('回调进入任务队列')
}, 0)

console.log('这行通常先执行')

如果主线程正在执行长任务,计时器、网络回调和输入事件都要等待合适的调度机会。微任务还会在当前任务结束后优先清空,这也是长微任务链会阻塞渲染的原因。

5. 消息循环

Chromium 内部使用消息泵、任务队列和多个调度器。原文中的 MessagePumpForIOMessagePumpForUIMessagePumpDefault 是 Chromium/平台实现中的历史内部概念,不是 Web 开发者可以依赖的三种固定线程。

对于页面脚本,更重要的是理解:

任务 -> JavaScript 执行 -> 微任务清空 -> 浏览器可能进行渲染

渲染机会、输入优先级、网络任务和后台页面节流会影响实际顺序。详细的页面事件循环可结合 requestAnimationFrame、Performance 面板和长任务数据观察。

五、浏览器进程模型名词的历史背景

早期 Chromium 资料中常见:

  • Process-per-site-instance:按站点实例复用进程;
  • Process-per-site:同一站点尽量复用;
  • Process-per-tab:每个标签页一个进程;
  • Single Process:单进程调试模式。

这些名称有助于理解历史设计和测试开关,但不是今天普通用户可以据此预测所有进程的规则。站点隔离、跨站 iframe、进程优先级、内存压力和平台实现会改变结果。

六、进程间通信(IPC)

不同进程通常不能直接共享普通堆内存,需要 IPC:

1. 管道

管道由操作系统维护缓冲区,适合按字节或消息传递。匿名管道常用于有父子关系的进程,命名管道也可以用于更广泛的本机通信。

2. 消息队列

消息队列按消息边界传输结构化数据,通常有大小、容量和生命周期限制。浏览器的 Mojo 等 IPC 框架会在消息传递上增加接口描述、权限和序列化机制。

3. 共享内存

多个进程映射同一段物理内存,可以减少大块数据复制,但必须配合锁、信号量、原子操作或版本协议解决同步、竞态和生命周期问题。共享内存不一定在所有场景都比消息传递更快。

4. 信号量和事件

信号量、互斥量、事件和条件变量用于协调多个执行实体访问共享资源。信号量本身不传递业务数据,只表达许可、计数或同步状态。

5. Socket

Socket 既可用于同机通信(Unix domain socket、命名管道等),也可用于跨主机通信。HTTP 请求是网络通信的一种应用协议,不应把 Socket 简化为“只用于不同主机”。

浏览器的 IPC 还会处理句柄传递、共享缓冲区、权限校验、崩溃通知和版本兼容;页面脚本通常只能使用经过安全抽象的 Web API,不能直接调用 Chromium 内部 IPC。

七、用户线程与内核线程模型(历史背景)

原文还介绍了用户线程与内核线程的一对一、多对一、多对多模型。这些内容属于操作系统和语言运行时的历史背景,不是 Chrome 页面里固定存在的三类线程。

  • 一对一:一个用户线程对应一个内核可调度线程,阻塞一个线程通常不会阻塞其他线程,也容易利用多核,但线程数量和内核调度成本更直接;
  • 多对一:多个用户线程由一个内核线程承载,用户态切换成本较低,但一个阻塞调用可能阻塞全部任务,也不能利用多个核心并行;
  • 多对多:多个用户线程映射到多个内核线程,由运行时和内核共同调度,灵活但实现复杂。

多核处理器和逻辑线程

一对一线程模型

多对一线程模型

多对多线程模型

现代系统的具体实现差异很大。浏览器的普通 JavaScript 执行环境不会让网页直接选择底层线程映射;页面能使用的是主线程、Worker、任务队列和标准通信 API。

观察进程、线程和 CPU

任务管理器可以帮助观察某个应用当前的进程、线程、CPU 和内存,但显示名称和列项随操作系统版本变化。浏览器任务管理器还可能把页面、GPU、网络和扩展服务分开显示。

系统任务管理器中的进程和线程

CPU 和内存使用情况

线程生命周期

教材常把线程描述为创建、就绪、运行、阻塞/等待和退出几个状态。实际操作系统还会区分睡眠、停止、等待锁、不可中断等待等状态,状态名称和转换也不完全相同。

进程生命周期

线程生命周期

八、多标签页之间如何通信

不同标签页通常不能直接读写彼此的 JavaScript 内存。常见中介方式包括:

  • BroadcastChannel:同源上下文广播;
  • storage 事件:通过 localStorage 通知其他文档;
  • window.open() + postMessage():在有窗口引用时定向通信;
  • Service Worker:向受控客户端转发消息;
  • SharedWorker:多个同源页面连接到同一个 Worker;
  • IndexedDB:保存可持久化状态,通常配合 BroadcastChannel 通知变化;
  • WebSocket:通过服务器中转,实现跨设备/跨标签实时同步;
  • Cookie 轮询:历史兼容方案,容量、延迟和安全性都较差。

这些方案的详细示例见跨标签页通信的 8 种方式

九、孤儿进程和僵尸进程

这是操作系统进程管理中的概念,不是浏览器标签页通信 API:

  • 孤儿进程:父进程退出后,子进程仍在运行,由操作系统的其他进程接管;
  • 僵尸进程:子进程已经退出,但父进程尚未读取退出状态,进程表中暂留记录。

现代浏览器通常由 Browser process 监控子进程并处理退出、崩溃和资源回收。网页开发者不能把“关闭标签页后一定留下僵尸进程”作为浏览器行为。

十、协程和用户态调度

协程是由语言运行时或库管理的可暂停执行单元,通常在达到 await、显式 yield 或其他挂起点时让出执行权。它与线程的区别取决于具体语言:

  • 有些协程在一个线程上协作调度;
  • 有些运行时会把任务调度到多个线程;
  • async/await 可能使用 Promise、事件循环和状态机实现;
  • Go 的 goroutine 由 Go runtime 调度到一个或多个系统线程,不是“固定一个协程对应一个内核线程”。

协程暂停与恢复示意

协程适合大量 I/O 等待和需要以同步风格表达异步流程的场景,但不会自动让 CPU 密集任务变快。一个没有主动让出的协程仍然可以阻塞所在执行线程。

async function loadUser() {
  const response = await fetch('/api/user')
  if (!response.ok) throw new Error('请求失败')
  return response.json()
}

在浏览器中,await 让出的是当前 async 函数的后续执行,不会创建一个新的 DOM 线程,也不会让后续代码绕过主线程限制。CPU 密集计算应考虑拆分任务、调度到 Worker 或使用合适的 WebAssembly/算法优化。

线程与协程的对比

对比项线程协程/异步任务
调度者通常由操作系统调度由语言运行时、库或事件循环调度
切换点可被抢占,也可能主动阻塞通常在显式挂起点或约定的异步点切换
内存有独立栈并共享进程资源运行时保存状态,大小和实现差异很大
并行能力多核上可以真正并行取决于运行时是否使用多个线程
适合场景CPU 并行、阻塞隔离、系统级调度大量 I/O 等待、轻量任务、异步流程
注意事项共享数据需要同步不等于不会竞态,也不自动避免长任务

不要把“线程固定 1MB、协程固定 2KB”或“协程只改三个寄存器”当作通用事实,这些数字和实现细节取决于操作系统、运行时、架构和编译器。

十一、总结

  • 进程提供资源和隔离边界,线程是进程内的执行单元;
  • 并发不等于并行,多核系统可以让线程真正并行;
  • 浏览器的进程数量和归属是动态实现细节,不是固定的“每标签页一个进程”;
  • 渲染进程主线程、合成线程、栅格工作线程和 Worker 共同完成页面工作,但线程数量和职责会随版本变化;
  • 计时器、网络回调和事件最终进入事件循环,不对应固定的 JS 专用线程;
  • IPC 可以使用消息、管道、共享内存和套接字,效率取决于数据量、复制、同步和安全要求;
  • 协程和 async/await 是控制异步流程的工具,不等于新线程、进程或自动并行;
  • 具体结论应使用浏览器任务管理器、DevTools Performance 和官方文档验证。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS