浏览器多进程架构
本节目标
- 理解浏览器为什么是「多进程」而不是「单进程」
- 掌握浏览器进程 / 渲染进程 / GPU 进程 / 网络进程的分工
- 用一段可运行的 Node 代码,真正「看到」进程隔离带来的好处
为什么要多进程:早期浏览器是单进程的——一个页面卡死或崩溃,整个浏览器跟着死。多进程的核心价值是隔离:把不同职责、不同站点放进各自的「盒子」(进程),一个盒子炸了,别的盒子照常工作。
Chrome 的主要进程(来自浏览器架构):
浏览器进程(Browser) :界面、地址栏、书签、网络调度、文件访问——全局只有一个
渲染进程(Renderer) :每个标签页(或每个站点)一个,负责解析 HTML/CSS、执行 JS、布局绘制
GPU 进程 :把图层交给显卡做合成与绘制(硬件加速)
网络进程(Network) :统一处理所有网络请求
插件 / 工具进程 :把扩展程序隔离出去,防止扩展拖垮浏览器渲染进程内部还有线程(这是后面事件循环、性能章节的伏笔):
主线程(Main) :解析 HTML/CSS、执行 JS、计算布局、绘制——JS 和渲染共用它
合成线程(Compositor):把图层拼成最终画面,滚动/动画常由它处理,不阻塞主线程
光栅线程(Raster) :把图层转成位图(像素)关键认知:JS 运行在主线程上,而主线程同时还要做解析、布局、绘制。所以一段很长的 JS 任务会「堵住」渲染——这正是后续「事件循环」与「性能优化」要解决的问题。
站点隔离(Site Isolation):现代 Chrome 不是「一个标签页一个渲染进程」,而是「一个站点一个渲染进程」。即使一个标签页里嵌了多个不同站点的 iframe,它们也各自独立。这既能防 Spectre 这类侧信道攻击,也能避免恶意页面偷读兄弟页面的内存。
下面这段 Node 代码,用「多进程」真实演示上面的隔离思想:主进程开两个「子标签」,杀掉其中一个,另一个和主进程都还在。
// 运行环境:Node.js 14+(无需任何第三方依赖)
// 步骤:把下面内容存为 brw-l1.js,终端执行 node brw-l1.js
// 你会看到:tab2 崩溃被杀了,但 tab1 和主进程依然活着 —— 这就是「进程隔离」
const { spawn } = require('node:child_process')
// 模拟一个「标签页」子进程:spawn 开一个带 IPC 通道的独立 Node 进程
function openTab(name) {
const child = spawn(process.execPath, ['-e',
// 子进程启动后,监听主进程发来的「自杀」指令
`process.on('message', (m) => { if (m === 'crash') { console.log('[${name}] 崩溃了,进程退出'); process.exit(1) } }); console.log('[${name}] 正常工作中...'); setInterval(() => {}, 1000)`
], { stdio: ['inherit', 'inherit', 'inherit', 'ipc'] })
child.on('exit', (code) => {
if (code !== 0) console.log(`>>> 主进程收到:${name} 已退出(但主进程和其他标签不受影响)`)
})
return child
}
const tab1 = openTab('tab1')
const tab2 = openTab('tab2')
// 3 秒后,让 tab2「崩溃」
setTimeout(() => {
console.log('--- 模拟 tab2 崩溃 ---')
tab2.send('crash')
}, 3000)
// 再 2 秒后,证明主进程和 tab1 仍然健康
setTimeout(() => {
console.log('主进程还活着,tab1 也还活着。隔离成功。')
tab1.send('crash')
process.exit(0)
}, 5500)在真实浏览器里怎么看:打开 Chrome → 右上角菜单 → 更多工具 → 任务管理器,你就能看到一个个独立的「进程」和它们各自占用的内存/CPU。
名词解释
进程(Process):操作系统分配资源的最小单位,拥有独立的内存空间。你可以把它理解成「一个带围墙的院子」——院子之间不能直接翻墙互看内存。方法/能力:fork()(操作系统创建子进程)、进程间通过「进程间通信(IPC)」交换消息、一个进程崩溃不会直接拖垮另一个。
线程(Thread):进程内部的「执行流」,同一进程里的线程共享同一块内存。可以看作「院子里的多个工人,共用院子里的材料」。方法:start()(启动线程)、join()(等它跑完)、yield()(主动让出 CPU)。JS 的主线程就是渲染进程里的一条线程。
站点隔离(Site Isolation):浏览器把「不同源站点」放进不同渲染进程的安全策略。它的核心方法/手段是「按源分配进程」+「跨进程内存不可互访」,目的是把安全边界从「标签页」细化到「站点」。
课后练习
练习 1:为什么「一个标签页里打开的恶意 iframe」不会直接偷走同标签页里银行页面的内存数据?
答案:因为站点隔离后,每个站点(即使是同一标签页里的不同 iframe)都运行在独立的渲染进程中,进程之间有操作系统的内存隔离。恶意 iframe 的进程无法直接读取银行页面进程的内存,必须由浏览器按规则通过 IPC 跨进程传递数据,而这类跨源读取会被浏览器拦截。
练习 2:既然多进程更安全更稳定,为什么浏览器不把「每一个 JS 函数」都放进独立进程?
答案:进程隔离的代价是「创建慢 + 内存占用大 + 通信成本高」。如果每个函数都开进程,光创建进程的开销就拖垮性能。所以浏览器只在「进程级」做隔离(稳定性/安全),而「线程级」的细粒度调度(如把长任务拆分)交给 JS 运行时去优化——成本和收益要平衡。
总结
我认为初学者最容易把「多进程」当成一句口号背下来,但真正重要的是理解它背后的取舍思维:隔离换来稳定和安全,代价是内存和通信开销。浏览器把隔离放在「进程」这一层,而不是「函数」或「标签页」层,是一个精心权衡的结果——既挡住了「一个页面崩溃全死」和「跨站偷内存」这两类最致命的问题,又没让开销失控。学这一节,你其实是在学「大型系统如何划分边界」。后面你会反复看到同一个思想:JS 和渲染挤在「主线程」这条窄路上会堵车,而事件循环、Web Worker、合成线程,本质上都是「给主线程分流」的不同手段。