浏览器内部如何使用共享内存?
在浏览器中使用共享内存(Shared Memory)主要是为了在多个执行上下文(线程、Worker、甚至不同页面)之间高效地共享数据,而不需要复制(zero-copy)。这在性能敏感、并发计算密集或实时性要求高的场景中特别重要。
一、背景:浏览器中的并发执行模型
浏览器中的 JavaScript 是单线程的(主线程 + 事件循环),为了避免阻塞 UI,浏览器引入了:
- Web Worker(后台线程)
- SharedWorker(多个页面共享)
- ServiceWorker(网络代理层)
- WebAssembly 线程
这些 Worker 拥有独立的内存空间,与主线程之间通常通过 postMessage() 进行通信。
但是 postMessage 会 复制 数据(结构化克隆),当数据量大或频繁传输时非常慢。
二、共享内存(Shared Memory)的核心机制
1. SharedArrayBuffer(SAB)
SharedArrayBuffer是浏览器中共享内存的核心对象。- 它允许多个线程(例如主线程 + Web Worker)访问同一块二进制内存。
- 数据不会复制,访问的是同一个物理缓冲区。
// 主线程
const sab = new SharedArrayBuffer(1024);
const arr = new Int32Array(sab);
const worker = new Worker("worker.js");
worker.postMessage(sab); // 传引用,不复制
// worker.js
onmessage = (e) => {
const arr = new Int32Array(e.data);
arr[0] = 42; // 主线程也能看到变化
};
2. Atomics 原子操作
由于多个线程可以同时访问同一块内存,浏览器提供 Atomics 对象用于:
- 安全地读写共享内存中的数据(原子性保证)
- 实现同步机制(例如锁、自旋、信号量)
Atomics.store(arr, 0, 123);
const value = Atomics.load(arr, 0);
Atomics.add(arr, 1, 1);
Atomics.wait(arr, 2, 0);
Atomics.notify(arr, 2, 1);
这些操作可以避免竞争条件(race condition)。
三、共享内存的典型用途
1. 多线程并行计算
在 WebAssembly 或 Web Worker 中,把数据块放入 SharedArrayBuffer 中,由多个 Worker 并行处理。
例如图像处理、视频解码、AI 模型推理:
主线程 -> 分配 SAB -> 多个 Worker 并行处理像素块 -> 主线程合并结果
这样可以充分利用多核 CPU。
2. 实时数据共享
在游戏、音频、视频流等场景中,需要低延迟共享数据:
- 主线程负责渲染或播放
- Worker 负责解码、数据准备
- 双方通过 SAB 实时共享帧数据或音频样本
3. WebAssembly 并发
WebAssembly 线程(使用 SharedArrayBuffer)可以像原生程序一样多线程运行。
比如在浏览器中运行 C/C++ 编译的物理引擎、机器学习框架时,会使用共享内存来进行线程间通信。
4. 浏览器内数据库或缓存共享
例如在同源的多个 tab 或 worker 之间共享大对象缓存(比如模型权重、纹理数据)。
以前做法是 IndexedDB + fetch + 再复制,现在用 SAB 可以让不同 Worker 直接访问相同的数据段,极大节省内存和加载时间。
四、安全限制与 COOP/COEP
由于 Spectre 类漏洞,SharedArrayBuffer 曾被禁用(2018年后重新引入,但受限制)。
要使用 SAB,网页必须启用:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
简称 COOP/COEP,以确保隔离性和安全。
五、现实中的典型应用场景
| 场景 | 说明 | 技术要点 |
|---|---|---|
| WebAssembly 多线程 | TensorFlow.js / OpenCV.js | SAB + Atomics |
| 音视频处理 | 实时混音、帧缓冲共享 | SAB + AudioWorklet |
| 游戏引擎 | 物理模拟、AI 逻辑线程 | SAB + Worker Pool |
| 数据可视化 | 后台计算聚合数据 | SAB + WebWorker |
| WebRTC 数据通道 | 低延迟传输共享缓冲 | SAB + Atomics |
六、共享内存总结
| 功能点 | 含义 |
|---|---|
| SharedArrayBuffer | 共享内存对象,允许多个线程访问同一块内存 |
| Atomics | 提供线程安全的原子操作与同步机制 |
| 优势 | 无复制、高性能、低延迟 |
| 限制 | 必须启用安全头(COOP/COEP) |
| 应用 | WebAssembly 并行计算、音视频处理、AI、游戏引擎、数据分析 |

七、为什么在共享内存较少的情况下,浏览器会报错 net::ERR_INSUFFICIENT_RESOURCES
🧩 一、先明确问题:
这里说的“共享内存太少”通常指:
SharedArrayBuffer的大小太小(例如几 KB)- 或系统允许的共享内存总量(OS 层限制)不足
- 或者浏览器线程池、信号量、同步等待队列资源不够
这些都会触发内部的 “resource exhaustion” 机制,从而出现统一的错误码:
**net::ERR_INSUFFICIENT_RESOURCES**。
🧠二、SharedArrayBuffer 并不是简单的 “数组”
创建 SAB 时,浏览器要向操作系统申请一块 共享内存页(Shared Memory Segment),这通常通过 mmap / shm_open / CreateFileMapping 实现。
即使你只申请了很小的空间(比如 1KB),浏览器仍要:
- 建立共享内存对象;
- 维护安全标识(COOP/COEP 隔离);
- 在主线程和 Worker 间注册共享句柄;
- 分配同步结构(Atomics.wait/notify 需要 futex-like 机制)。
这些元数据结构通常比数据本身更大。
换句话说:SAB 小并不代表系统负担小。
⚙️三、当 SAB 太小或数量太多时的问题
🧱 1. 内存页粒度限制
现代系统(尤其是 Linux / macOS)分配共享内存是按页(通常 4KB 或 64KB)为单位。
每个 SAB 不管多小,至少占用一个内存页,再加上额外的句柄/描述符。
如果你频繁创建许多“小 SAB”,例如:
for (let i = 0; i < 20000; i++) {
new SharedArrayBuffer(1024); // 1KB × 20000
}
看似只用 20MB,但实际上:
- 需要 20,000 个内核级共享段;
- 每个段都要内核对象、句柄、同步元数据;
- 最终耗尽浏览器进程的文件描述符或共享内存句柄上限。
这时 Chromium 就会抛出:
net::ERR_INSUFFICIENT_RESOURCES
🧩 2. futex / wait queue 资源不足
如果你在大量 SAB 上使用 Atomics.wait() / Atomics.notify() 实现同步,浏览器会为每个 SAB 分配对应的 futex-like 等待队列。
当等待队列太多或资源池耗尽,也会触发相同错误码。
(V8 会尝试 fallback,但 Chrome 层仍会报 net::ERR_INSUFFICIENT_RESOURCES)
🧩 3. 浏览器的安全隔离上下文限制
启用了 COOP/COEP 后,每个共享内存区域都要经过 “MemoryCoordinator” 注册,
以防止跨源泄漏和 Spectre 攻击。
当浏览器认为页面的“共享内存句柄”过多、或系统共享区不足时,会提前拒绝分配。
这也是 ERR_INSUFFICIENT_RESOURCES 出现的根源之一 —— 浏览器主动拒绝创建新的共享段。
📉 四、典型触发场景
| 场景 | 问题描述 | 错误原因 |
|---|---|---|
| 创建大量小 SAB | 每个 SAB 占 1 页,句柄爆炸 | 句柄/文件描述符耗尽 |
| Worker 循环中频繁创建 SAB | 无复用共享段 | 共享内存区耗尽 |
| 频繁使用 Atomics.wait/notify | 等待队列太多 | futex 资源耗尽 |
| WASM 多线程小块共享 | 内部内存碎片过多 | 区域分配失败 |
| 浏览器低资源模式(移动端) | 系统共享页不足 | 操作系统拒绝分配 |
💡 五、正确做法与优化建议
- 复用一块大 SAB,不要创建很多小的。
const sab = new SharedArrayBuffer(1024 * 1024); // 一次分配 1MB
const views = [];
for (let i = 0; i < 100; i++) {
views.push(new Int32Array(sab, i * 1024, 256));
}
这样只占一个共享段,降低句柄压力。
- 控制 Worker 数量:线程数量过多也会导致资源消耗放大。
- 避免短生命周期 SAB:频繁创建/销毁共享内存比复用更容易触发内存泄漏。
- 监控浏览器内存分配:使用
chrome://tracing或chrome://memory-internals/检查 SharedArrayBuffer 分配。 - 合理粒度:推荐每个 SAB 至少 64KB,避免页碎片。
🧭 六、总结
| 项目 | 含义 |
|---|---|
| 错误本质 | 浏览器资源(共享内存页、句柄、队列)耗尽 |
| 触发条件 | 小块 SAB 太多 / futex 太多 / 系统共享段不足 |
| 错误码含义 | Chromium 统一“资源耗尽”错误,而非真正网络错误 |
| 解决方案 | 合并小块、复用内存、减少 SAB 数量 |

本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19513661

浙公网安备 33010602011771号