SharedArrayBuffer

SharedArrayBuffer 对象用来表示一个通用的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,但它可以用来在共享内存上创建视图。与可转移的 ArrayBuffer 不同,SharedArrayBuffer 不是可转移对象

描述

要在集群中的一个代理(agent,可以是网页的主程序或其任意一个 web worker)与另一个代理之间使用 ShareArrayBuffer 共享内存,需要使用 postMessage结构化克隆

结构化克隆算法接受 SharedArrayBuffer 对象和映射到 SharedArrayBuffer 对象的类型化数组。在这两种情况下, SharedArrayBuffer 对象会被传输给接收者,从而在接收代理中产生一个新的、私有的 SharedArrayBuffer 对象(就像 ArrayBuffer 一样)。但是,两个 SharedArrayBuffer 对象指向的共享数据块其实是同一个数据块,一个代理中对数据块的修改最终会将在另一个代理中可见。

js
const sab = new SharedArrayBuffer(1024);
worker.postMessage(sab);

共享内存可以被 worker 线程或主线程创建和同时更新。根据系统(CPU、操作系统、浏览器)的不同,需要一段时间才能将变化传递给所有上下文环境。因此需要通过原子操作来进行同步。

SharedArrayBuffer 被一些 web API 使用,比如:

安全需求

由于幽灵漏洞,共享内存和高精度定时器在 2018 年 1 月 5 日开始被禁用。在 2020 年,一种新的、安全的方法已经被标准化,以重新启用共享内存。

作为基本要求,你的文档需要处于一个安全上下文中。

对于顶级文档,需要设置两个标头来实现你网站的跨源隔离:

http
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

为了验证跨源隔离是否生效,你可以测试窗口和 worker 线程上下文中是否存在 crossOriginIsolated 属性:

js
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 (en-US) 标头会限制你对弹出窗口引用的保留能力。两个顶级窗口上下文之间的直接访问基本上只在它们同源且携带相同的两个标头(且具有相同的值)时才可行。

API 可用性

根据是否采取了上述安全措施,各类内存共享 API 具有不同的可用性:

  • Atomics 对象总是可用的。
  • SharedArrayBuffer 对象在原则上始终可用,但遗憾的是,除非设置了前面提到的两个标头,否则其在全局对象上的构造函数是隐藏的,这是为了兼容 web 内容。这个限制有望在未来被移除。尽管如此,仍然可以用 WebAssembly.Memory 来获取实例。
  • 除非设置了上文提到的两个标头,否则各种 postMessage() 的 API 在处理 SharedArrayBuffer 对象时会抛出异常。如果正确设置了这两个标头,Window 对象和专用 worker 线程上的 postMessage() 都可以正常工作,并允许跨线程共享内存。

WebAssembly 共享内存

WebAssembly.Memory 对象可以通过设置 shared (en-US) 构造函数标志来创建。当这个标志设置为 true 时,构造出的 Memory 对象就像 SharedArrayBuffer 一样,可以通过 postMessage() 在 worker 线程之间共享,而且 Memory 对象的后备 buffer (en-US) 是一个 SharedArrayBuffer。因此,上述关于在 worker 线程间共享 SharedArrayBuffer 的要求同样适用于共享 WebAssembly.Memory

WebAssembly Thread 提案还定义了一套新的原子指令。就像 SharedArrayBuffer 及其方法始终可用(并且只有在设置了新标头的情况下,才允许线程间共享)一样,WebAssembly 原子指令也是始终可用的。

增大 SharedArrayBuffer

SharedArrayBuffer 对象可以通过在调用 SharedArrayBuffer() 时包含 maxByteLength 选项来使其可增大。你可以通过访问 SharedArrayBuffergrowablemaxByteLength 属性来分别查询其是否可增大以及其最大大小。你还可以通过调用 grow() 为一个可增大的 SharedArrayBuffer 分配新的大小。新字节被初始化为 0。

这些特性令增大 SharedArrayBuffer 更为高效——否则,你必须创建一个新大小的缓冲区副本。它还使得 JavaScript 在这方面与 WebAssembly 保持一致(Wasm 线性内存可以通过 WebAssembly.Memory.prototype.grow() (en-US) 调整大小)。

出于安全原因,SharedArrayBuffer 的大小无法缩小,只能增大。

构造函数

SharedArrayBuffer()

创建一个新的 SharedArrayBuffer 对象。

静态属性

SharedArrayBuffer[@@species]

返回用于构造 SharedArrayBuffer 方法返回值的构造函数。

实例属性

属性定义于 SharedArrayBuffer.prototype 并且被所有 SharedArrayBuffer 实例所共享。

SharedArrayBuffer.prototype.byteLength

数组大小,以字节为单位。在构造数组时被确定,并且只能在可增大的 SharedArrayBuffer上通过 SharedArrayBuffer.prototype.grow() 方法来改变。

SharedArrayBuffer.prototype.constructor

创建实例对象的构造函数。对于 SharedArrayBuffer 实例,其初始值为 SharedArrayBuffer 构造函数。

SharedArrayBuffer.prototype.growable

只读。如果当前 SharedArrayBuffer 可以增大,则返回 true,否则返回 false

SharedArrayBuffer.prototype.maxByteLength

当前 SharedArrayBuffer 可以增大的最大长度,只读,以字节为单位。在构造数组时确定且无法更改。

SharedArrayBuffer.prototype[@@toStringTag]

@@toStringTag 属性的初始值是字符串 "SharedArrayBuffer"。它被用于 Object.prototype.toString()

实例方法

SharedArrayBuffer.prototype.grow()

增大当前 SharedArrayBuffer 到指定大小,以字节为单位。

SharedArrayBuffer.prototype.slice()

返回一个新的 SharedArrayBuffer,其内容是当前 SharedArrayBufferbegin(含)到 end(不含)的字节的副本。如果 beginend 为负,则它是从数组的末尾开始的索引,而不是数组的开头。

示例

创建一个新的 SharedArrayBuffer

js
const sab = new SharedArrayBuffer(1024);

截取 SharedArrayBuffer

js
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 中使用

js
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

参见