由于Spectre和Meltdown的漏洞,所有主流浏览器在2018年1月就禁用了sharedArrayBuffer。
从2019年开始,有些浏览器开始逐步重新启用这一特性。
既不克隆,也不转移,sharedArrayBuffer作为ArrayBuffer能够在不同浏览器上下文间共享。
在把sharedArrayBuffer传给postMessage()时,浏览器只会传递原始缓冲区的引用。
结果是,两个不同的JavaScript上下文会分别维护对同一个内存块的引用。每个上下文都可以随意修改这个缓冲区,就跟修改常规ArrayBuffer一样。
多个上下文访问SharedArrayBuffer时,如果同时对缓冲区执行操作,就可能出现资源争用问题。
Atomics API通过强制同一时刻只能对缓冲区执行一个操作,可以让多个上下文安全地读写一个SharedArrayBuffer。
SharedArrayBuffer
SharedArrayBuffer和ArrayBuffer具有同样的API。
在多个执行上下文间共享内存意味着并发线程操作成为可能。
传统JavaScript操作对于并发内存访问导致的资源争用没有提供保护。
Atomics API解决了这个问题,可以保证SharedArrayBuffer上的JavaScript操作是线程安全的。
原子操作基础
任何全局上下文中都有一个Atomics 对象,对象上暴露了用于执行线程安全操作的一套静态方法。
SharedArrayBuffer 和 Atomics API 详解
一、SharedArrayBuffer(共享内存)
1. 基本概念
共享内存:允许多个线程(Web Workers)共享同一块内存区域
主要用途:在多线程环境中高效地共享和操作数据
特点:在 Web Workers 之间传递时不复制数据,而是共享引用
2. 基本使用
javascript
// 主线程 const sharedBuffer = new SharedArrayBuffer(1024); // 创建1KB共享内存 const int32View = new Int32Array(sharedBuffer); // 创建视图 // 传递给 Worker const worker = new Worker('worker.js'); worker.postMessage(sharedBuffer); // Worker 中接收 // self.onmessage = function(e) { // const sharedBuffer = e.data; // const int32View = new Int32Array(sharedBuffer); // }3. 安全要求
必须设置 COOP/COEP 头部:
http
Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp
二、Atomics API
1. 为什么需要 Atomics?
解决多线程并发访问时的竞争条件问题
确保操作的原子性(atomic operations)
提供内存同步机制
2. 主要方法分类
原子操作(读写和运算):
javascript
const sharedBuffer = new SharedArrayBuffer(16); const intArray = new Int32Array(sharedBuffer); // 基础原子操作 Atomics.store(intArray, 0, 42); // 原子写入 let value = Atomics.load(intArray, 0); // 原子读取 // 原子运算 Atomics.add(intArray, 0, 5); // 原子加法(返回旧值) Atomics.sub(intArray, 0, 3); // 原子减法 Atomics.and(intArray, 0, 0b1111); // 原子与运算 Atomics.or(intArray, 0, 0b1100); // 原子或运算 Atomics.xor(intArray, 0, 0b1010); // 原子异或运算 // 原子交换 let old = Atomics.exchange(intArray, 0, 100); // 交换为新值 let result = Atomics.compareExchange( intArray, 0, expectedValue, newValue ); // 仅在当前值等于期望值时交换同步和等待:
javascript
// 等待和通知机制(类似锁) Atomics.wait(intArray, index, expectedValue, timeout); Atomics.notify(intArray, index, count); // 唤醒等待的线程 // 使用示例 // Worker 1: Atomics.store(intArray, 0, 0); Atomics.wait(intArray, 0, 0); // 等待值变为非0 // Worker 2: Atomics.store(intArray, 0, 1); Atomics.notify(intArray, 0, 1); // 唤醒一个等待的Worker3. 典型用例示例
生产者-消费者模式:
javascript
// 创建共享内存 const sharedBuffer = new SharedArrayBuffer(12); const sharedArray = new Int32Array(sharedBuffer); // 主线程(生产者) const worker = new Worker('consumer.js'); worker.postMessage(sharedBuffer); // 生产者逻辑 function produce(data) { // 等待缓冲区有空位 while (Atomics.load(sharedArray, 0) !== 0) { Atomics.wait(sharedArray, 0, 1); } // 写入数据 Atomics.store(sharedArray, 2, data); // 设置标志通知消费者 Atomics.store(sharedArray, 0, 1); Atomics.notify(sharedArray, 0, 1); } // Worker(消费者) self.onmessage = function(e) { const sharedArray = new Int32Array(e.data); while (true) { // 等待有数据可读 if (Atomics.load(sharedArray, 0) === 0) { Atomics.wait(sharedArray, 0, 0); continue; } // 读取数据 const data = Atomics.load(sharedArray, 2); // 处理数据 console.log('Consumed:', data); // 重置标志通知生产者 Atomics.store(sharedArray, 0, 0); Atomics.notify(sharedArray, 0, 1); } };互斥锁实现:
javascript
class Mutex { constructor(sharedArray, index = 0) { this.lock = new Int32Array(sharedArray); this.index = index; } acquire() { while (true) { // 尝试获取锁(0表示未锁定) if (Atomics.compareExchange(this.lock, this.index, 0, 1) === 0) { return; } // 等待锁释放 Atomics.wait(this.lock, this.index, 1); } } release() { // 释放锁 Atomics.store(this.lock, this.index, 0); Atomics.notify(this.lock, this.index, 1); } execute(callback) { this.acquire(); try { return callback(); } finally { this.release(); } } }三、最佳实践和注意事项
1.性能考虑
尽量减少共享内存的访问
使用适当大小的内存块
避免频繁的跨线程通信
2. 错误处理
javascript
try { // 检查浏览器支持 if (typeof SharedArrayBuffer !== 'undefined') { const buffer = new SharedArrayBuffer(1024); } } catch (error) { console.error('SharedArrayBuffer not supported:', error); }3. 调试技巧
使用断点和内存查看器
添加详细的日志记录
实现超时机制防止死锁
四、浏览器支持和安全限制
支持情况:
Chrome 68+(需要安全上下文)
Firefox 79+
Safari 15.4+
Edge 79+
安全限制:
必须使用 HTTPS
需要正确的 HTTP 头部
某些 API 可能被限制使用
总结
SharedArrayBuffer 和 Atomics API 为 JavaScript 带来了真正的多线程编程能力,但同时也增加了复杂性。使用时需要注意:
线程安全:始终使用原子操作访问共享内存
性能:避免不必要的共享访问
调试:多线程调试较为困难,需有良好设计
兼容性:检查浏览器支持并准备降级方案
这些 API 特别适用于:
高性能计算
实时数据处理
游戏和图形应用
大规模并行计算任务
Atomics 对象方法总结表
| 方法类别 | 方法名 | 语法 | 描述 | 返回值 |
|---|---|---|---|---|
| 原子操作 | store() | Atomics.store(typedArray, index, value) | 原子方式将给定值存储在数组的指定位置 | 设置的值 |
load() | Atomics.load(typedArray, index) | 原子方式从数组的指定位置读取值 | 读取的值 | |
exchange() | Atomics.exchange(typedArray, index, value) | 原子方式将指定位置的值替换为新值 | 替换前的旧值 | |
compareExchange() | Atomics.compareExchange(typedArray, index, expectedValue, replacementValue) | 仅在当前值等于期望值时原子替换为新值 | 替换前的旧值(无论是否替换) | |
| 原子运算 | add() | Atomics.add(typedArray, index, value) | 原子加法:将指定值加到当前位置的值上 | 运算前的旧值 |
sub() | Atomics.sub(typedArray, index, value) | 原子减法:从当前位置的值减去指定值 | 运算前的旧值 | |
and() | Atomics.and(typedArray, index, value) | 原子按位与:与指定值进行按位与运算 | 运算前的旧值 | |
or() | Atomics.or(typedArray, index, value) | 原子按位或:与指定值进行按位或运算 | 运算前的旧值 | |
xor() | Atomics.xor(typedArray, index, value) | 原子按位异或:与指定值进行按位异或运算 | 运算前的旧值 | |
| 同步等待 | wait() | Atomics.wait(typedArray, index, expectedValue, timeout) | 使线程等待,直到指定位置的值发生变化或超时 | "ok"(正常唤醒),"timed-out"(超时),"not-equal"(值不匹配) |
notify() | Atomics.notify(typedArray, index, count) | 唤醒正在等待指定位置的线程 | 成功唤醒的线程数量 | |
| 实用方法 | isLockFree(size) | Atomics.isLockFree(size) | 检查指定大小的操作是否在硬件层面是无锁的 | true(无锁)或false(需要锁) |
参数说明表
| 参数 | 类型 | 描述 |
|---|---|---|
typedArray | Int8ArrayUint8ArrayInt16ArrayUint16ArrayInt32ArrayUint32ArrayBigInt64ArrayBigUint64Array | 共享的TypedArray对象,基于SharedArrayBuffer创建 |
index | number | 在typedArray中操作的索引位置 |
value | number或BigInt | 要存储、添加或操作的值(类型需与TypedArray匹配) |
expectedValue | number或BigInt | 期望的当前值(用于compareExchange和wait) |
replacementValue | number或BigInt | 替换的新值 |
timeout | number | 最大等待时间(毫秒),可选,默认无限等待 |
count | number | 要唤醒的等待线程数量,可选,默认唤醒所有 |
size | number | 字节大小(通常为1、2、4、8、16) |
使用场景速查表
| 场景 | 推荐方法 | 示例用途 |
|---|---|---|
| 基本读写 | store()/load() | 简单的线程安全读写操作 |
| 计数器 | add()/sub() | 多线程共享计数器 |
| 状态标志 | compareExchange() | 实现锁、信号量等同步原语 |
| 线程等待 | wait()/notify() | 生产者-消费者模式、条件等待 |
| 位操作 | and()/or()/xor() | 位标志、状态机控制 |
| 数据交换 | exchange() | 无锁队列、缓冲区交换 |
注意事项表
| 项目 | 说明 |
|---|---|
| 类型一致性 | TypedArray的类型必须与操作的值类型匹配 |
| 索引边界 | 索引必须在TypedArray的有效范围内 |
| 超时处理 | wait()的timeout参数控制最大等待时间 |
| 线程唤醒 | notify()可指定唤醒线程数量,避免"惊群效应" |
| 性能优化 | isLockFree()可帮助选择最优的数据大小 |
| 浏览器支持 | 部分浏览器可能对某些TypedArray类型支持有限 |
典型示例代码片段
javascript
// 创建共享内存 const sharedBuffer = new SharedArrayBuffer(1024); const int32Array = new Int32Array(sharedBuffer); // 原子写入和读取 Atomics.store(int32Array, 0, 42); let value = Atomics.load(int32Array, 0); // 42 // 原子运算 let oldValue = Atomics.add(int32Array, 0, 10); // 返回42,位置0的值变为52 // 比较并交换 let result = Atomics.compareExchange(int32Array, 0, 52, 100); // 成功返回52 // 等待和通知 // 线程1: Atomics.wait(int32Array, 0, 100); // 等待值改变 // 线程2: Atomics.store(int32Array, 0, 200); Atomics.notify(int32Array, 0, 1); // 唤醒一个等待线程