SharedArrayBuffer
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since December 2021.
SharedArrayBuffer
对象用来表示一个通用的原始二进制数据缓冲区,类似于 ArrayBuffer
对象,但它可以用来在共享内存上创建视图。与可转移的 ArrayBuffer
不同,SharedArrayBuffer
不是可转移对象。
描述
要在集群中的一个代理(agent,可以是网页的主程序或其任意一个 web worker)与另一个代理之间使用 ShareArrayBuffer
共享内存,需要使用 postMessage
和结构化克隆。
结构化克隆算法接受 SharedArrayBuffer
对象和映射到 SharedArrayBuffer
对象的类型化数组。在这两种情况下, SharedArrayBuffer
对象会被传输给接收者,从而在接收代理中产生一个新的、私有的 SharedArrayBuffer
对象(就像 ArrayBuffer
一样)。但是,两个 SharedArrayBuffer
对象指向的共享数据块其实是同一个数据块,一个代理中对数据块的修改最终会将在另一个代理中可见。
const sab = new SharedArrayBuffer(1024);
worker.postMessage(sab);
共享内存可以被 worker 线程或主线程创建和同时更新。根据系统(CPU、操作系统、浏览器)的不同,需要一段时间才能将变化传递给所有上下文环境。因此需要通过原子操作来进行同步。
SharedArrayBuffer
被一些 web API 使用,比如:
安全需求
由于幽灵漏洞,共享内存和高精度定时器在 2018 年 1 月 5 日开始被禁用。在 2020 年,一种新的、安全的方法已经被标准化,以重新启用共享内存。
作为基本要求,你的文档需要处于一个安全上下文中。
对于顶级文档,需要设置两个标头来实现你网站的跨源隔离:
Cross-Origin-Opener-Policy
设置为same-origin
(来保护你的源站点免受攻击)Cross-Origin-Embedder-Policy
设置为require-corp
或credentialless
(保护受害者免受你的源站点的影响)
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
为了验证跨源隔离是否生效,你可以测试窗口和 worker 上下文中的 Window.crossOriginIsolated
或 WorkerGlobalScope.crossOriginIsolated
属性:
const myWorker = new Worker("worker.js");
if (crossOriginIsolated) {
const buffer = new SharedArrayBuffer(16);
myWorker.postMessage(buffer);
} else {
const buffer = new ArrayBuffer(16);
myWorker.postMessage(buffer);
}
在设置了这两个标头后,postMessage()
不再为 SharedArrayBuffer
对象抛出错误,因此,跨线程共享内存现在可用。
嵌套文档和专用 worker 线程也需要将 Cross-Origin-Embedder-Policy
标头设置为同样的值。对于同源嵌套文档和子资源,不需要进行任何其他更改。同站(但跨源)嵌套文档和子资源需要将 Cross-Origin-Resource-Policy
标头设置为 same-site
。而它们的跨源(和跨站点)的对应部分也需要将同样的标头设置为 cross-origin
。请注意,将 Cross-Origin-Resource-Policy
标头设置为除 same-origin
之外的任何值,都会使资源暴露于潜在的攻击中,比如幽灵漏洞。
请注意,Cross-Origin-Opener-Policy
标头会限制你对弹出窗口引用的保留能力。两个顶级窗口上下文之间的直接访问基本上只在它们同源且携带相同的两个标头(且具有相同的值)时才可行。
API 可用性
根据是否采取了上述安全措施,各类内存共享 API 具有不同的可用性:
Atomics
对象总是可用的。SharedArrayBuffer
对象在原则上始终可用,但遗憾的是,除非设置了前面提到的两个标头,否则其在全局对象上的构造函数是隐藏的,这是为了兼容 web 内容。这个限制有望在未来被移除。尽管如此,仍然可以用WebAssembly.Memory
来获取实例。- 除非设置了上文提到的两个标头,否则各种
postMessage()
的 API 在处理SharedArrayBuffer
对象时会抛出异常。如果正确设置了这两个标头,Window
对象和专用 worker 线程上的postMessage()
都可以正常工作,并允许跨线程共享内存。
WebAssembly 共享内存
WebAssembly.Memory
对象可以通过设置 shared
构造函数标志来创建。当这个标志设置为 true
时,构造出的 Memory
对象就像 SharedArrayBuffer
一样,可以通过 postMessage()
在 worker 线程之间共享,而且 Memory
对象的后备 buffer
是一个 SharedArrayBuffer
。因此,上述关于在 worker 线程间共享 SharedArrayBuffer 的要求同样适用于共享 WebAssembly.Memory
。
WebAssembly Thread 提案还定义了一套新的原子指令。就像 SharedArrayBuffer
及其方法始终可用(并且只有在设置了新标头的情况下,才允许线程间共享)一样,WebAssembly 原子指令也是始终可用的。
增大 SharedArrayBuffer
SharedArrayBuffer
对象可以通过在调用 SharedArrayBuffer()
时包含 maxByteLength
选项来使其可增大。你可以通过访问 SharedArrayBuffer
的 growable
和 maxByteLength
属性来分别查询其是否可增大以及其最大大小。你还可以通过调用 grow()
为一个可增大的 SharedArrayBuffer
分配新的大小。新字节被初始化为 0。
这些特性令增大 SharedArrayBuffer
更为高效——否则,你必须创建一个新大小的缓冲区副本。它还使得 JavaScript 在这方面与 WebAssembly 保持一致(Wasm 线性内存可以通过 WebAssembly.Memory.prototype.grow()
调整大小)。
出于安全原因,SharedArrayBuffer
的大小无法缩小,只能增大。
构造函数
-
创建一个新的
SharedArrayBuffer
对象。
静态属性
-
返回用于构造
SharedArrayBuffer
方法返回值的构造函数。
实例属性
属性定义于 SharedArrayBuffer.prototype
并且被所有 SharedArrayBuffer
实例所共享。
-
数组大小,以字节为单位。在构造数组时被确定,并且只能在可增大的
SharedArrayBuffer
上通过SharedArrayBuffer.prototype.grow()
方法来改变。 -
创建实例对象的构造函数。对于
SharedArrayBuffer
实例,其初始值为SharedArrayBuffer
构造函数。 -
只读。如果当前
SharedArrayBuffer
可以增大,则返回true
,否则返回false
。 -
当前
SharedArrayBuffer
可以增大的最大长度,只读,以字节为单位。在构造数组时确定且无法更改。 -
[Symbol.toStringTag]
属性的初始值是字符串"SharedArrayBuffer"
。它被用于Object.prototype.toString()
。
实例方法
-
增大当前
SharedArrayBuffer
到指定大小,以字节为单位。 -
返回一个新的
SharedArrayBuffer
,其内容是当前SharedArrayBuffer
从begin
(含)到end
(不含)的字节的副本。如果begin
或end
为负,则它是从数组的末尾开始的索引,而不是数组的开头。
示例
创建一个新的 SharedArrayBuffer
const sab = new SharedArrayBuffer(1024);
截取 SharedArrayBuffer
sab.slice(); // SharedArrayBuffer { byteLength: 1024 }
sab.slice(2); // SharedArrayBuffer { byteLength: 1022 }
sab.slice(-2); // SharedArrayBuffer { byteLength: 2 }
sab.slice(0, 1); // SharedArrayBuffer { byteLength: 1 }
在 WebGL buffer 中使用
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, sab, gl.STATIC_DRAW);
规范
Specification |
---|
ECMAScript Language Specification # sec-sharedarraybuffer-objects |
浏览器兼容性
BCD tables only load in the browser
参见
Atomics
ArrayBuffer
- JavaScript 类型化数组指南
- Web Workers
- 共享内存——简明教程,TC39 ecmascript-sharedmem 提案
- JavaScript 新并发原语的初体验,hacks.mozilla.org(2016)
- COOP 和 COEP 的解释,由 Chrome 团队撰写(2020)
Cross-Origin-Opener-Policy
Cross-Origin-Embedder-Policy
Cross-Origin-Resource-Policy
Window.crossOriginIsolated
和WorkerGlobalScope.crossOriginIsolated
- Android Chrome 88 和桌面版 Chrome 92 中的 SharedArrayBuffer 更新,developer.chrome.google.cn(2021)