SharedArrayBuffer

SharedArrayBuffer具有和ArrayBuffer几乎相同的 API 调用接口,不同之处在于,SharedArrayBuffer是不可转移对象。调用postMessage(message[, transfer])时不能将该对象放入第二个参数transfer的列表中。

然而,当把SharedArrayBuffer或在其上建立的视图对象通过结构化克隆进行传递时,只会传递其指向实际数据的引用,所以不会导致数据的复制,而是会共享相同的内存空间。

配置启用 SharedArrayBuffer

由于安全上的要求,使用SharedArrayBuffer必须启用跨源隔离,配置要求为:

  • 页面的响应头需要设置
    • Cross-Origin-Opener-Policy: same-origin
    • Cross-Origin-Embedder-Policy: require-corp
  • 页面的域名为localhost或协议为https:

通过检查全局对象的crossOriginIsolated属性来确定跨源隔离是否生效。

验证数据共享特性

我们可以通过将数据传递给 Worker 并修改数据,然后在 Worker 中异步地延迟读取数据,看看能否读到修改后的数据,来验证SharedArrayBuffer是否能够共享内存数据。通过postMessage()在标签页间传递数据的方法也是可以的。

// 主线程
const buffer = new SharedArrayBuffer(8);
const arr = new Uint16Array(buffer);

const worker = new Worker("worker.js");
worker.postMessage(buffer);
worker.postMessage(arr);
arr[0] = 257;
// Worker线程
addEventListener("message", e => {
  setTimeout(() => {
    if (e.data instanceof SharedArrayBuffer) {
      console.log(new Uint8Array(e.data));
    } else {
      console.log(e.data);
    }
  });
});
/* 输出:
Uint8Array(8) [1, 1, 0, 0, 0, 0, 0, 0]
Uint16Array(4) [ 257, 0, 0, 0 ]
*/

worker.postMessage()后立即修改了数据,如果数据是按复制传递的,那么之后的修改不会影响 Worker 中接受到的数据,反之,则修改对 Worker 可见。在 Worker 中通过setTimeout使得读取消息数据的操作会在下一轮消息循环中执行,因而能保证主线程在postMessage()之后的代码能先执行完毕。从输出结果来看,Worker 线程看到了主线程的修改,因而证实了SharedArrayBuffer的内存数据是共享的。

避免数据竞争

数据共享就会带来竟态条件(Race Condition),运行时提供了Atomics命名空间,其下包含了许多用于控制原子操作的方法。感兴趣的读者请自行查阅相关资料,本书不作展开。

增长长度

对于指定了maxByteLengthArrayBuffer,可以动态伸缩长度。而对于SharedArrayBuffer来说,只能增长大小,而不能收缩。

实例属性growable取代了resizable,表示该SharedArrayBuffer是否可增长。实例方法grow(newLength)取代了resize(newLength)用于增长SharedArrayBuffer的长度,不超过maxByteLength

const growable = new SharedArrayBuffer(4, { maxByteLength: 16 });
growable.byteLength; // 4

growable.grow(8); // 8
growable.byteLength;

growable.grow(4);
// throws Invalid length parameter