进程、线程与 IPC
基于 Chromium 现代架构 · 核于 2026-07
速查
- CPU:计算机的「大脑」,擅长顺序处理五花八门的任务;现代 CPU 多核,每核可独立干活
- GPU:擅长同时在海量小核上跑简单任务,为图形而生;页面合成、动画上屏靠它
- 进程(process)= 应用「正在执行的程序」:OS 启动它时划给一块私有内存;进程结束,OS 整块回收
- 线程(thread)= 住在进程里的执行单元,可执行进程程序的任意部分;同进程内线程共享内存
- 崩溃半径不同:一个线程崩 → 整个进程崩;一个进程崩 → 其他进程无恙——多进程架构的立足点
- 进程之间内存互不可见,协作必须走 IPC(Inter-Process Communication,进程间通信)
- 进程可以请求 OS 再启动新进程分担工作(如 browser 进程拉起 renderer);新进程有自己的内存,多份进程会重复占内存
- Chrome 里的关键 IPC:browser → renderer 的 commit navigation、renderer → browser 的 onload 完成通知、compositor 帧提交给 Viz
- 对前端:JS 跑在某个 renderer 的主线程;
postMessage(页面 ↔ Worker、页面 ↔ 跨站 iframe)本质就是「不共享内存、传消息」的同款思路
一、先分清两块硬件:CPU 与 GPU
理解浏览器架构,要先从它脚下的硬件与操作系统说起。
**CPU(Central Processing Unit,中央处理器)**是计算机的大脑。一个 CPU 核心像一位全能工人:什么任务都能接,但一次干一件,按顺序来。现代 CPU 通常有多个核心,意味着可以同时有几位「全能工人」并行干活。
**GPU(Graphics Processing Unit,图形处理器)**走另一条路:单个核心很「笨」、只擅长简单任务,但胜在数量极多、同时开工。它最初为图形处理而生——把百万像素各自算色值,正是「简单任务 × 海量并行」的典型场景;如今 GPU 也被用于图形之外的通用计算。
对前端的意义:浏览器把「算」和「画」拆给两者——解析、布局、跑 JS 主要在 CPU;把已经画好的图层合成上屏则交给 GPU。这是后面「compositor 线程 / Viz 进程」的硬件背景,也是 transform/opacity 动画便宜的底层原因(细节见浏览器渲染原理)。
顺手记两个 JS 里就能摸到的硬件接口:
// 逻辑核心数:给 Worker 池定容量的常用依据(注意是逻辑核,含超线程)
console.log(navigator.hardwareConcurrency); // 例如 8
// 设备内存档位(GB,取值被刻意粗化为 0.25/0.5/1/2/4/8):
// Chrome 决定进程数上限时看的也是这类硬件水位
console.log(navigator.deviceMemory); // 例如 8二、进程与线程:一块私有内存和里面的执行者
应用程序跑起来时,OS 视角看到的是进程。启动一个应用 = OS 创建一个进程,并划出一块私有的内存空间(memory slab)供它存放一切状态。线程则住在进程内部,负责实际执行程序的任意片段;一个进程可以只有一条线程,也可以有很多条。
┌─ 进程 A ──────────────┐ ┌─ 进程 B ──────────────┐
│ 私有内存(互不可见) │ │ 私有内存 │
│ ├─ 线程 1 ──┐ │ IPC │ ├─ 线程 1 │
│ ├─ 线程 2 ──┼─ 共享 A │◄────►│ └─ 线程 2 │
│ └─ 线程 3 ──┘ 的内存 │ │ │
└───────────────────────┘ └───────────────────────┘两者最关键的差异是内存归属与崩溃半径:
| 维度 | 同进程内的多线程 | 多个进程 |
|---|---|---|
| 内存 | 共享进程的私有内存,随手互读互写 | 彼此不可见,各有一块 |
| 通信成本 | 低(直接读写共享状态) | 高(必须走 IPC 传消息) |
| 一处崩溃 | 整个进程一起死 | 只死自己,其他进程照常 |
| 资源回收 | — | 进程关闭时 OS 整块回收其内存 |
「线程崩 → 进程崩」值得多咀嚼一句:线程们共享同一块内存,一条线程把内存写坏,同进程的所有线程都不可信了,OS 只能整个进程收掉。反过来,进程之间因为内存隔离,一个进程再怎么崩也污染不到别人——这就是浏览器选择多进程的全部理由的种子:把「容易崩、不可信」的网页代码圈进独立进程,崩溃和恶意行为都被圈在墙内(展开见多进程架构)。
三、程序如何被 OS 执行
把链条串起来:
- 启动:你双击浏览器图标,OS 创建 browser 进程、分配私有内存,程序开始在其线程上执行。
- 派生:进程可以请求 OS 创建新进程去分担工作——browser 进程为每个 tab 拉起 renderer 进程正是这一机制。新进程拿到自己的内存;如果两边要共用数据,只能复制一份过去或传消息。
- 代价:因为内存不共享,公共基础设施会在每个进程里各存一份(Chrome 里最典型的是 V8 引擎的拷贝)——进程越多,重复内存越多。
- 回收:进程退出(正常或崩溃),OS 把那块私有内存整体收回。一个 renderer 崩了,它占的内存立刻还给系统,别的进程毫发无损。
3.1 调度:线程多于核心怎么办
真正被 CPU 核心执行的单位是线程。系统里就绪的线程数远多于核心数,OS 调度器按**时间片(time slice)**把线程轮流分派到各核心上,切换时保存/恢复现场(上下文切换,有成本)。两点浏览器相关的推论:
- 「浏览器几十个进程」不等于「几十个核心在烧」——大多数线程大部分时间在等(等网络、等 IPC、等 vsync),调度器只把核心分给有活干的线程;
- 同一进程内的线程可以被调度到不同核心真正并行(raster 线程 ×N 正是为了吃满多核),而 JS 主线程再忙也只占一个核——想利用多核,只能开 Worker。
更深的调度算法属操作系统课范畴;对理解浏览器架构,抓住「进程 = 私有内存 + 崩溃边界,线程 = 调度与执行单元」即可。
四、IPC:隔离之后,怎么协作
进程彼此看不见对方内存,但浏览器的各部分必须协作——地址栏在 browser 进程,页面内容在 renderer 进程,画面上屏在 Viz 进程。解法是 IPC(Inter-Process Communication,进程间通信):进程之间通过操作系统提供的通道互发消息,而不是共享变量。
Chrome 中几条贯穿本叶的典型 IPC:
| 消息 | 方向 | 时机 |
|---|---|---|
| commit navigation | browser → renderer | 响应数据就绪,通知 renderer 接管导航并随附数据流 |
| navigation commit 确认 | renderer → browser | renderer 确认接管,browser 才更新地址栏与会话历史 |
| onload 完成 | renderer → browser | 所有 frame 的 onload 跑完,browser 停掉 tab 的 spinner |
| compositor frame 提交 | renderer → Viz | 合成器产出一帧,交给 Viz 聚合上屏 |
| 导航请求 | renderer → browser | 页面内点链接 / JS 改 window.location 发起新导航 |
消息传递比共享内存慢,但换来了边界清晰:每条跨进程交互都是显式的、可审计的——沙箱里的 renderer 想做特权操作(发网络请求、写文件),只能「打报告」请 browser/服务进程代办,而不是自己伸手。
五、前端视角:你早就在写「多进程风格」的代码
- JS 的单线程指的是:你的代码默认只跑在某个 renderer 进程的主线程上。浏览器整体高度并行,但分给你的执行权只有这一条线程——主线程被长任务占住,解析、渲染、交互全排队。
- Web Worker / Service Worker 是把 JS 挪到同进程(或其他进程)的其他线程;它们与页面不共享内存,靠
postMessage传消息——与 IPC 同一种世界观。 - 跨站 iframe 在站点隔离下干脆真的在另一个进程里(见站点隔离),
postMessage此时就是货真价实的跨进程 IPC。 - 与网络分层的类比:进程隔离 + IPC 之于浏览器,正如「分层 + 协议」之于网络(见网络分层)——都是用明确边界换取可组合与可控。
把 Web 平台的通信原语按「共享 or 传消息」归类,会发现它完整复刻了本页的取舍:
| 原语 | 模型 | 备注 |
|---|---|---|
postMessage + 结构化克隆 | 传消息(数据复制) | 页面 ↔ Worker、页面 ↔ iframe;大对象复制有成本 |
Transferable(如 ArrayBuffer) | 传消息(所有权转移) | 零拷贝,转移后原持有方不可再用 |
SharedArrayBuffer | 真·共享内存 | 正因像「同进程线程」般危险,Spectre 后要求跨源隔离(COOP+COEP 头)才开放 |
BroadcastChannel | 传消息(一对多) | 同源的多个 tab/Worker 广播 |
SharedArrayBuffer 的遭遇是本叶主题的绝佳注脚:一旦允许共享内存,就等于把「同进程」的攻击面(高精度计时 → Spectre 侧信道)交给了网页,于是浏览器反过来要求页面先用 COOP/COEP 声明自我隔离——进程/内存边界的每一次放松,都要用更强的隔离承诺来赎买。
小结
CPU 擅长顺序通用计算、GPU 擅长海量并行的简单任务,浏览器把「算」与「画」分派给两者。进程是 OS 分配了私有内存的运行中程序,线程住在进程里共享其内存:线程崩会拖死整个进程,进程之间却互不牵连——这条「崩溃半径」差异是浏览器多进程架构的立足点。隔离带来协作问题,答案是 IPC 消息传递:commit navigation、onload 通知、合成帧提交都是跨进程消息。前端里的 Worker 与 postMessage 正是同一世界观在应用层的投影。下一页看 Chrome 如何用这些积木搭出多进程架构。