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 使用，比如：

* `WebGLRenderingContext.bufferData()`
* `WebGLRenderingContext.bufferSubData()` (en-US)
* `WebGL2RenderingContext.getBufferSubData()` (en-US)
### 安全需求

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

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

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

* `Cross-Origin-Opener-Policy` (en-US) 设置为 `same-origin`（来保护你的源站点免受攻击）
* `Cross-Origin-Embedder-Policy` 设置为 `require-corp` 或 `credentialless`（保护受害者免受你的源站点的影响）


```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` 选项来使其可增大。你可以通过访问 `SharedArrayBuffer` 的 `growable` 和 `maxByteLength` 属性来分别查询其是否可增大以及其最大大小。你还可以通过调用 `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`，其内容是当前 `SharedArrayBuffer` 从 `begin`（含）到 `end`（不含）的字节的副本。如果 `begin` 或 `end` 为负，则它是从数组的末尾开始的索引，而不是数组的开头。

示例
--

### 创建一个新的 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 with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `Atomics`
* `ArrayBuffer`
* JavaScript 类型化数组指南
* Web Workers
* 共享内存——简明教程，TC39 ecmascript-sharedmem 提案
* JavaScript 新并发原语的初体验，hacks.mozilla.org（2016）
* COOP 和 COEP 的解释，由 Chrome 团队撰写（2020）
* `Cross-Origin-Opener-Policy` (en-US)
* `Cross-Origin-Embedder-Policy`
* `Cross-Origin-Resource-Policy`
* `crossOriginIsolated`
* Android Chrome 88 和桌面版 Chrome 92 中的 SharedArrayBuffer 更新，developer.chrome.com（2021）