Chrome 打开一个网页,竟偷偷开了 5 个“小号”?揭秘浏览器的多进程“分身术”

104 阅读8分钟

Chrome 打开一个网页,竟偷偷开了 5 个“小号”?揭秘浏览器的多进程“分身术”

面试官问:“你了解 Chrome 的多进程架构吗?”
别再只答“每个 tab 一个进程”了——今天带你从源码视角,拆解 Chrome 启动时到底在后台干了啥!


你以为打开的是一个网页?其实启动的是一个“微型操作系统”

当你双击 Chrome 图标,或者在地址栏输入 https://juejin.cn 并按下回车——
你以为只是加载了一个页面?
实际上,Chrome 已经悄悄拉起了一整套多进程协作系统,像一支训练有素的特种部队,各司其职、互不干扰。

而这一切,正是现代浏览器对抗“崩溃、卡顿、安全漏洞”三大顽疾的核心武器。

本文将结合 Chromium 源码逻辑(非完整代码,但基于真实架构),深入剖析:
✅ 为什么 Chrome 要用多进程?
✅ 打开一个页面,到底启动了哪些进程?
✅ 这些进程如何通信?JS 单线程为何还能高效运行?
✅ 高频面试题背后的底层原理是什么?


一、进程与线程:浏览器架构的骨架与神经

要理解 Chrome 的多进程模型,必须先厘清两个基础但常被混淆的概念:进程(Process)线程(Thread)

🧱 进程:资源分配的最小单位

  • 每个进程拥有独立的内存空间(堆、栈、全局变量等)。
  • 一个进程崩溃,不会影响其他进程(操作系统负责隔离与回收)。
  • 创建/销毁进程开销大(需分配虚拟内存、文件描述符等)。

💡 在 Chrome 中,每个标签页默认对应一个独立的渲染进程(Renderer Process) ,实现故障隔离。

🧠 线程:CPU 调度与执行的最小单位

  • 线程共享所属进程的内存空间(包括堆和全局数据)。
  • 同一进程内的多个线程可并发执行(多核 CPU 下真正并行)。
  • 线程创建/切换开销远小于进程。

⚠️ 但共享内存也带来竞态条件(Race Condition) 风险,需通过锁、原子操作等同步机制解决。

🔗 进程与线程的关系:1:N 模型

  • 一个进程至少包含一个主线程
  • 一个进程可创建多个线程,共同完成复杂任务。
  • 线程不能跨进程直接访问数据——必须通过 IPC(进程间通信)。

🖥️ Chrome 中的典型进程-线程结构

以一个普通标签页为例:

1. Browser Process(主进程)
  • 线程组成

    • UI Thread:处理浏览器窗口、地址栏、书签等交互。
    • IO Thread:管理磁盘 I/O、网络代理(旧版)、IPC 消息收发。
    • File Thread / DB Thread:处理本地存储(如 IndexedDB)。
  • 特点:全局唯一,是整个浏览器的“指挥中心”。

2. Renderer Process(渲染进程)
  • 线程组成

    • Main Thread(主线程)

      • 执行 JavaScript(V8 引擎)
      • 解析 HTML/CSS → 构建 DOM/CSSOM → 布局(Layout)→ 绘制(Paint)
      • JS 是单线程的根源就在这里! (避免 DOM 操作冲突)
    • Compositor Thread(合成线程)

      • 处理 CSS 动画、滚动、transform 等无需重排的操作
      • 将图层提交给 GPU 进程光栅化
    • Worker Threads(可选)

      • Web Workers、Service Workers、Audio Worklets 等运行在此
      • 真正的多线程 JS 执行环境(但无法操作 DOM)

关键设计:即使 JS 阻塞主线程,合成线程仍可流畅滚动页面(得益于分层渲染架构)。

3. GPU Process
  • 主要运行 GPU Main Thread 和 Command Buffer 线程
  • 接收来自多个 Renderer 的绘图指令,统一提交给显卡驱动。
4. Network Process
  • 内部使用 多线程连接池(如 QUIC、HTTP/2 流复用)。
  • 但对 Renderer 来说,它只是一个“黑盒服务”,通过 IPC 请求资源。

🌟 总结一句话
进程负责“隔离与安全”,线程负责“并发与效率”
Chrome 用多进程防崩,用多线程提速——二者相辅相成,缺一不可。

二、从“单进程 IE”到“多进程 Chrome”:一场浏览器架构的革命

❌ 单进程浏览器的致命缺陷(以 IE 为例)

  • 所有功能(UI、渲染、JS、网络)跑在同一个进程中。
  • 一旦 JS 死循环或插件崩溃 → 整个浏览器挂掉
  • 内存泄漏、安全沙箱缺失 → 用户数据极易被窃取。

💡 关键概念进程是资源分配的最小单位。一个进程崩溃,OS 会回收其全部内存,但不会影响其他进程。

✅ Chrome 的多进程架构:隔离 + 安全 + 稳定

Chromium(Chrome 开源内核)采用 Multi-Process Architecture,核心思想是:功能模块化 + 进程隔离

当你打开一个新标签页访问掘金,Chrome 至少会启动以下 5 类进程

进程类型职责是否每个 Tab 独立?
Browser Process(主进程)管理 UI、协调子进程、存储、网络调度全局唯一
Renderer Process(渲染进程)解析 HTML/CSS、执行 JS(V8)、合成页面默认每个 Tab 一个
GPU Process处理 3D 渲染、CSS 动画、Canvas 加速全局共享
Network Process统一处理所有网络请求(HTTP/HTTPS/WebSocket)全局共享
Utility / Storage Process本地存储(IndexedDB、LocalStorage)、Cookie、缓存管理可能共享或按需创建

📌 注意:现代 Chrome 为节省内存,对同源页面可能复用渲染进程(Site Isolation 机制下仍隔离)。


三、源码视角:一个页面加载时,Chromium 做了什么?

虽然我们无法在此贴出数百万行 Chromium C++ 代码,但可以还原其关键调用链与设计逻辑

// 简化版伪代码:BrowserMainLoop 启动流程
int BrowserMain(...) {
  // 1. 创建主消息循环(Message Loop)
  MessageLoop main_loop;

  // 2. 初始化 BrowserProcessImpl(主进程单例)
  g_browser_process = new BrowserProcessImpl();

  // 3. 启动必要服务进程
  g_browser_process->CreateNetworkService();   // → Network Process
  g_browser_process->CreateGpuService();       // → GPU Process
  g_browser_process->CreateStoragePartition(); // → Storage Process

  // 4. 用户打开新 Tab → 创建 WebContents
  WebContents* contents = WebContents::Create(params);
  //    ↓
  //    RendererProcessHost::CreateRenderProcess() 
  //        → 启动独立 Renderer Process(含 Blink + V8)
}

关键设计解析:

1. Renderer Process = Blink + V8
  • Blink:负责 HTML/CSS 解析、布局(Layout)、绘制(Paint)。
  • V8:执行 JavaScript,单线程(主线程),但通过 Web Workers 支持多线程(受限于同源策略)。
  • 为什么 JS 是单线程?
    避免 DOM 操作的竞态条件(如两个线程同时修改 <div> 会导致渲染树不一致)。
    → 于是有了 Event Loop 机制处理异步任务。
2. Network Process:统一出口,安全可控
  • 所有 Tab 的网络请求都经由同一个网络进程发出。

  • 好处:

    • 复用连接池(HTTP/2 多路复用)
    • 统一代理、证书校验、缓存策略
    • 防止恶意页面直接访问系统网络栈
3. GPU Process:不只是“显卡加速”
  • 早期只为 3D CSS (transform: translate3d),现在连普通页面滚动、合成(Compositing)都走 GPU。
  • 合成线程(Compositor Thread) 在 Renderer 中生成图层,提交给 GPU 进程光栅化 → 减轻主线程负担。

例如,在 Renderer Process 初始化时:

// RendererMain() 启动后
void RenderThreadImpl::Init() {
  // 创建主线程上下文(V8 + Blink)
  blink::Initialize();

  // 启动合成线程(Compositor)
  compositor_thread_ = base::Thread::Create("Compositor");
  
  // 注册 IPC 通道,连接 Browser Process
  channel_ = IPC::ChannelProxy::Create(...);
}

🔍 注意:Compositor Thread 是独立于 JS 主线程的,即使 while(true) 死循环,页面滚动仍可能流畅(若仅涉及 transform/opacity)。


四、进程间如何通信?IPC 机制详解

进程隔离虽好,但数据怎么传

Chromium 使用 Mojo IPC(取代旧的 Chromium IPC),基于消息传递模型。

例如:Renderer 想发 AJAX 请求:

// 前端 JS
fetch('/api/user')

→ V8 执行 → Blink 触发网络请求 →
Renderer Process 通过 Mojo 发送 URLRequest 消息 →
Network Process 接收并执行实际 HTTP 请求 →
→ 响应数据通过 Mojo 回传 →
→ Renderer 解析 JSON 并更新 DOM。

🔒 安全边界:Renderer 进程运行在沙箱(Sandbox) 中,无权直接访问文件系统、网络、设备。所有敏感操作必须通过主进程代理。


五、延伸思考:高频面试题背后的原理

Q1:为什么 JS 是单线程?如何实现异步?

  • 答案:避免 DOM 操作冲突。异步靠 Event Loop + Task Queue + Microtask Queue
  • 深入setTimeout 属于宏任务,Promise.then 是微任务,V8 在每轮事件循环末尾清空微任务队列。

Q2:Chrome 多进程 vs Electron 多窗口?

  • Electron 默认每个窗口一个 Renderer 进程(类似 Chrome Tab),但可配置 nodeIntegrationcontextIsolation 控制安全级别。

Q3:内存占用大怎么办?

  • Chrome 采用 Site Isolation(站点隔离)后,内存更高,但防 Spectre 攻击。

  • 优化手段:

    • 合并同源 Tab 的 Renderer
    • 冻结后台 Tab(Throttling)
    • 使用 SharedArrayBuffer + WASM 实现高性能计算(需跨域隔离)

Q4:如何调试多进程?

  • Chrome 地址栏输入:chrome://inspectchrome://process-internals
  • DevTools 的 Performance 面板可查看主线程、合成线程、Worker 线程的活动。

Q5:JS 为什么是单线程?那 Web Worker 算不算多线程?

  • :JS 主线程单线程是为了保证 DOM 操作的一致性
    Web Worker 是独立 JS 环境,运行在 Renderer 进程的另一个线程中,无 DOM 访问权限,通过 postMessage 与主线程通信。

Q6:Event Loop 是线程还是进程?

  • :Event Loop 是主线程上的一个循环机制,用于调度宏任务(setTimeout、I/O)和微任务(Promise、MutationObserver)。
    它本身不是线程,但依赖线程存在。

Q7:为什么 Chrome 内存占用高?

  • :多进程 = 多份 V8 实例 + 多份 Blink 上下文 + 多份缓存。
    但换来的是稳定性、安全性、并行能力——这是现代 Web 应用复杂度提升的必然代价。

六、结语:浏览器,是最复杂的前端应用

Chrome 不仅仅是一个“看网页的工具”,它是一个集操作系统、虚拟机、图形引擎、网络安全中心于一体的超级应用

理解其多进程架构,不仅能回答面试题,更能:

  • 设计更健壮的前端性能方案(如避免长任务阻塞主线程)
  • 理解 Web Worker / SharedWorker 的适用边界
  • 评估 PWA、Electron、Tauri 等框架的底层差异

下次当同事抱怨“Chrome 吃内存”时,你可以微微一笑:

“那是因为它在为你默默守护每一个标签页的安全与流畅。”