Skip to content

浏览器进程与线程 ​

问题 ​

进程和线程有什么区别?Chrome 的多进程架构是什么?浏览器渲染进程有哪些线程?

结论 ​

进程 vs 线程 ​

维度进程线程
定义资源分配和拥有的最小单位CPU 调度和执行的最小单位
资源拥有独立内存空间共享所属进程的内存和资源
隔离崩溃不影响其他进程一个线程崩溃可能影响整个进程
通信IPC(进程间通信),成本较高直接读写共享内存,成本低
开销创建/切换开销大创建/切换开销小

一句话总结:进程是资源容器,线程是执行单元;同一进程内的线程共享内存,不同进程之间相互隔离。

Chrome 多进程架构 ​

Chrome 采用多进程架构,主要包含以下 5 类进程:

进程职责
浏览器进程负责界面显示、用户交互、子进程管理、存储等
GPU 进程处理 GPU 任务(3D 绘制、视频解码等),独立运行防止 GPU 驱动崩溃影响整体
网络进程负责网络资源加载(HTTP 请求等)
渲染进程负责将 HTML/CSS/JS 渲染成页面;运行在沙箱中,崩溃不影响浏览器主进程
插件进程每个插件独立运行,插件崩溃不影响浏览器

为什么要多进程?:单进程时任意标签页崩溃会导致整个浏览器崩溃;多进程隔离后,一个标签页渲染进程崩溃只影响该标签页,整体稳定性大幅提升。

渲染进程的线程组成 ​

渲染进程是前端最关注的进程,内部包含以下 5 种线程:

① GUI 渲染线程

  • 解析 HTML/CSS,构建 DOM 树、CSSOM 树、渲染树
  • 负责页面重绘(repaint)和回流(reflow)
  • 与 JS 引擎线程互斥:JS 执行时 GUI 线程挂起,GUI 更新进入队列等待

② JS 引擎线程

  • 执行 JavaScript 脚本(V8 引擎)
  • 同一时刻只有一个 JS 引擎线程在运行(JavaScript 单线程)
  • 长时间执行会阻塞 GUI 渲染,导致页面卡顿

③ 事件触发线程

  • 控制事件循环(Event Loop)
  • 当 JS 引擎执行 setTimeout/ajax/点击事件等时,将回调添加到此线程管理的任务队列
  • 等 JS 引擎空闲时,将队首任务推入执行

④ 定时器触发线程

  • 专门负责 setTimeout / setInterval 计时
  • 计时结束后将回调推入事件队列(不直接执行)
  • 注意:W3C 规定定时器最小精度为 4ms,小于 4ms 的一律按 4ms 处理

⑤ 异步 HTTP 请求线程

  • XHR 发起请求后,浏览器新开一个线程处理网络请求
  • 状态变更时,若有回调函数,将其推入事件队列等待执行

为什么 JS 是单线程?:如果 JS 可以多线程同时操作 DOM,就可能出现两个线程同时修改同一个 DOM 节点的竞态问题,浏览器无法决定以哪个结果为准。通过单线程 + 事件循环的模型,JS 保证了 DOM 操作的安全性。

进程间通信方式(IPC) ​

进程之间不共享内存,需要通过 IPC 机制传递数据,常见方式有六种:

① 管道(Pipe)

  • 内核中的缓冲区,进程 A 写入、进程 B 读取
  • 只能单向通信,只能在有血缘关系的进程间使用,生命周期随进程

② 消息队列(Message Queue)

  • 消息的链表,支持按消息类型独立接收
  • 避免命名管道的阻塞问题,但每条消息有最大长度限制

③ 信号量(Semaphore)

  • 本质是计数器,用于进程间的互斥与同步
  • 例:信号量为 1,进程 A 访问时置 0,进程 B 见 0 则等待

④ 信号(Signal)

  • Unix 系统中最古老的 IPC 方式
  • 操作系统通知进程某种预定义事件(如 SIGKILL、SIGTERM)

⑤ 共享内存(Shared Memory)

  • 最快的 IPC 方式:多个进程映射同一块物理内存
  • 通常需要配合信号量防止竞态条件

⑥ Socket

  • 可跨主机通信(网络 IPC)
  • 浏览器发起 HTTP 请求就是 Socket 通信的典型例子

僵尸进程和孤儿进程 ​

  • 孤儿进程:父进程退出,但子进程仍在运行。孤儿进程会被 init(PID=1)进程收养,由 init 负责状态收集。
  • 僵尸进程:子进程已结束,但父进程没有调用 wait() 释放其资源,子进程的进程描述符仍留在系统中。僵尸进程无法被 kill,只能等父进程释放或结束。

死锁产生的原因及解决 ​

死锁的四个必要条件(四者同时满足才会死锁):

条件说明
互斥条件资源在某时刻只能被一个进程占用
请求与保持进程持有资源的同时,还在请求其他资源
不可剥夺进程已获得的资源只能由自己释放,不可强制剥夺
环路等待存在进程—资源的环形等待链

预防死锁的方法(破坏上述条件之一):

  • 资源一次性分配(破坏"请求与保持")
  • 请求新资源前先释放已有资源(破坏"不可剥夺")
  • 按固定顺序申请资源(破坏"环路等待")

浏览器多标签页通信 ​

标签页之间没有共享内存,需要借助中介者传递消息,常见方案:

  • WebSocket:通过服务器中转,服务器向各标签页推送消息
  • SharedWorker:多个页面共享同一个 Worker 线程,线程作为中介
  • localStorage + storage 事件:一个标签页写入,另一个监听 storage 事件
  • BroadcastChannel(现代浏览器):同源页面间直接广播消息
  • postMessage:持有目标窗口引用时可直接发消息

Service Worker 的理解 ​

Service Worker 是运行在浏览器背景中的独立线程(不能直接访问 DOM),必须使用 HTTPS 协议。

主要用途:离线缓存、请求拦截、后台同步、消息推送。

实现缓存的基本步骤:注册 → 安装时缓存文件 → 拦截 fetch 返回缓存。

javascript
// 注册
if (navigator.serviceWorker) {
  navigator.serviceWorker.register('sw.js')
}

// sw.js:安装时缓存
self.addEventListener('install', e => {
  e.waitUntil(
    caches.open('my-cache').then(cache =>
      cache.addAll(['./index.html', './index.js'])
    )
  )
})

// 拦截请求,有缓存直接返回
self.addEventListener('fetch', e => {
  e.respondWith(
    caches.match(e.request).then(response => response || fetch(e.request))
  )
})

参考来源 ​