JavaScript 引擎自行访问和管理内存,并为编写和执行的每个程序或代码块分配内存,同时对内存中不存在的数据执行垃圾回收。

尽管 JavaScript 是一种内存管理语言,但也能管理数据,只是有缺陷。例如,JavaScript 可以为特定程序或变量分配超过内存所需的可用空间,当然这可能拖慢 JavaScript 的垃圾收集器。
为了让开发人员能够在多个线程之间分配和共享数据,引擎引入了 ArrayBuffer 和 SharedArrayBuffer。但是,Chrome 92 后需要站点支持跨域隔离 (cross-origin isolated),开发者可以在顶级文档上发送两个 HTTP 标头,从而允许访问 SharedArrayBuffer 等 Web API 并防止外部攻击(Spectre 攻击、跨源攻击等)。
Cross-Origin-Opener-Policy: same-origin// 隔离当前页面与浏览器中的任何跨源弹出窗口Cross-Origin-Embedder-Policy: require-corp// 确保从当前网站加载的所有资源都已使用 CORP 加载值得注意的是,COEP(Cross-Origin-Embedder-Policy) 标头会破坏每个需要与浏览器中的跨源窗口通信的集成,例如: 来自第三方服务器的身份验证和付款等。通过在文档的顶层设置标头,则站点会处于安全的环境中,并提供对 Web API 的访问。
2. 什么是 SharedArrayBuffer2.1 ArrayBuffer 代表字节数组在讨论 SharedArrayBuffer 时很容易关注到 Shared、Array 和 Buffer 等字样。
Array 用于存储由不同数据类型(字符串、布尔值、数字和对象)组成的数据元素的数据结构,Buffer 是内存存储的一部分,用于在发送或接收数据之前临时存储数据。

而 ArrayBuffer 是一种与其他数组不同的数组,即 字节数组,其只接受字节,例如下面的示例:
const buffer = new ArrayBuffer(8);// 创建一个 Int32Array 视图,指向同一个 ArrayBufferconst int32View = new Int32Array(buffer);// 设置值int32View[0] = 42;int32View[1] = 17;console.log(int32View[0]);// 输出: 42console.log(int32View[1]);// 输出: 172.2 SharedArrayBuffer 结构化克隆与跨线程共享要在 JavaScript 中使用共享内存,开发者可以创建 SharedArrayBuffer,该对象创建一个新的对象构造函数,用于在多个线程之间写入和共享数据。

SharedArrayBuffer 表示通用的原始二进制数据缓冲区,类似于 ArrayBuffer ,但可用于在共享内存上创建视图。同时 SharedArrayBuffer 是不可传输的对象,这点与 ArrayBuffer 不同 。
为了使用 SharedArrayBuffer 对象从集群 (Cluster) 中的一个代理与另一个代理共享内存(代理可以是主程序也可以是 Worker),可以使用 postMessage 和 结构化克隆。
const sab = new SharedArrayBuffer(1024);worker.postMessage(sab);结构化克隆用于复制复杂的 JavaScript 对象,在调用 structuredClone() 时内部使用,用于通过 postMessage() 在 Workers 之间传输数据、使用 IndexedDB 存储对象等场景。
结构化克隆算法接受 SharedArrayBuffer 对象和映射到 SharedArrayBuffer 对象的类型化数组。在这两种情况下,SharedArrayBuffer 对象都会传输到接收方,从而在接收代理中产生一个 新的私有 SharedArrayBuffer 对象(就像 ArrayBuffer 一样)。但是,两个 SharedArrayBuffer 对象引用的共享数据块是同一个,并且一个代理中块的副作用最终会在另一个代理中显现出来。
let {SharedArrayBuffer=sayTheLineBart() } = globalThis;console.assert(crossOriginIsolated === false, "The case of interest is specific to non-isolated");try { structuredClone(new SharedArrayBuffer(1)); console.assert(false, "Shouldn’t clone SAB at all without cross-origin isolation.");} catch (err) { console.assert(err.code === DOMException.DATA_CLONE_ERR, "Should throw DataCloneError");}function sayTheLineBart() { return new WebAssembly.Memory({ initial: 0, maximum: 0, shared: 1 }).buffer.constructor;}共享内存可以在 Worker 线程或主线程中同时创建和更新。根据系统 CPU、操作系统、浏览器等的不同,更改可能需要一段时间才能传播到所有上下文。如果需要同步,则可以利用原子操作。
2018 年 1 月 5 日,由于在现代 CPU 架构中发现漏洞攻击,SharedArrayBuffer 在所有主流浏览器中被禁用。
后来 SharedArrayBuffer 在 Google Chrome v67 中重新启用,目前可以在启用了 ` 站点隔离功能 ` 的平台上使用,可防止 Spectre 漏洞攻击并使网站更安全。
3. 如何使用 SharedArrayBuffer前面讲过,使用 SharedArrayBuffer 的一个好处是能够在 JavaScript 中共享内存。在 JavaScript 中,Web Worker 是创建 JS 线程的一种方式。
Web Worker 可以与 SharedArrayBuffer 一起使用,其通过直接指向每个数据存储或之前访问过的内存来实现 Web Worker 之间原始二进制数据的共享。
<DOCTYPE html><html><head> <script type="text/JavaScript" src="script.js"></script></head><body> <script type="text/JavaScript" src="worker.js"></script></body></html>下面是 worker.js 的核心代码:
const newWorker = new Worker('worker.js');const buffMemLength = new SharedArrayBuffer(1024);// 1024 字节长度let typedArr = new Int16Array(buffMemLength);// 初始化原始数据typedArr[0] = 20;// 将数据传递给 workernewWorker.postMessage(buffMemLength);为了与工作线程共享主线程的数据,工作线程设置了一个 message 事件监听器,在接收到 data 时运行并修改数据。
let BYTE_PER_LENTH = 5;addEventListener('message', ({ data}) => { var arr = new Int16Array(data); console.group('[worker thread]'); console.log('Data received from main thread: %i', arr[0]); console.groupEnd(); // 从 worker 线程更新数据 let dataChanged = 5 * BYTE_PER_LENTH; arr[0] = dataChanged; // 通知主线程 postMessage('Updated');})同时,开发者可以在主线程添加一个 onmessage 事件接受 Worker 线程的数据更新事件:
const newWorker = new Worker('worker.js');const buffMemLength = new SharedArrayBuffer(1024);var typedArr = new Int16Array(buffMemLength);typedArr[0] = 20;newWorker.postMessage(buffMemLength);// 添加 onmessagenewWorker.onmessage = (e) => { console.group('[the main thread]'); console.log('Data updated from the worker thread: %i', typedArr[0]); console.groupEnd();}同步共享内存非常重要,其可以在多线程同时运行时不会发生意外更改,例如:数据不一致等。为了在共享内存中加入同步,开发人员需要使用原子操作 (Atomics)。
原子操作确保每个进程在下一个进程之前连续执行,并且所有从内存读取或写入特定内存的数据都在 wait() 和 notify() 方法的辅助下一个接一个地执行。下面的示例表示写入线程存储新值并在写入完成后通知等待线程:
const sab = new SharedArrayBuffer(1024);const int32 = new Int32Array(sab);console.log(int32[0]); // 0;Atomics.store(int32, 0, 123);Atomics.notify(int32, 0, 1);参考资料https://blog.logrocket.com/understanding-sharedarraybuffer-and-cross-origin-isolation/
https://zhuanlan.zhihu.com/p/11809045697
https://webreflection.medium.com/about-sharedarraybuffer-atomics-87f97ddfc098
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics
https://www.hongkiat.com/blog/shared-memory-in-javascript/
https://blog.persistent.info/2021/08/worker-loop.html