javascript基础学习系列三百一十五:SharedArrayBuffer

SharedArrayBuffer 与 ArrayBuffer 具有同样的 API。二者的主要区别是 ArrayBuffer 必须 在不同执行上下文间切换,SharedArrayBuffer 则可以被任意多个执行上下文同时使用。
在多个执行上下文间共享内存意味着并发线程操作成为了可能。传统 JavaScript 操作对于并发内存 访问导致的资源争用没有提供保护。下面的例子演示了 4 个专用工作线程访问同一个 SharedArrayBuffer 导致的资源争用问题:

const workerScript = `
    self.onmessage = ({data}) => {
      const view = new Uint32Array(data);
// 执行1000000次加操作
for (let i = 0; i < 1E6; ++i) {
// 线程不安全加操作会导致资源争用
view[0] += 1;
}
      self.postMessage(null);
    };
`;
const workerScriptBlobUrl = URL.createObjectURL(new Blob([workerScript]));
// 创建容量为 4 的工作线程池 const workers = [];
for (let i = 0; i < 4; ++i) {
      workers.push(new Worker(workerScriptBlobUrl));
    }
// 在最后一个工作线程完成后打印出最终值 let responseCount = 0;
for (const worker of workers) {
      worker.onmessage = () => {
        if (++responseCount == workers.length) {
          console.log(`Final buffer value: ${view[0]}`);
        }
}; }
// 初始化SharedArrayBuffer
const sharedArrayBuffer = new SharedArrayBuffer(4); const view = new Uint32Array(sharedArrayBuffer); view[0] = 1;
// 把 SharedArrayBuffer 发送到每个工作线程 for (const worker of workers) {
      worker.postMessage(sharedArrayBuffer);
    }
//(期待结果为 4000001。实际输出可能类似这样:) // Final buffer value: 2145106

Atomics 与 SharedArrayBuffer

为解决这个问题,Atomics API 应运而生。Atomics API 可以保证 SharedArrayBuffer 上的

JavaScript 操作是线程安全的。

原子操作基础

注意 SharedArrayBufferAPI等同于ArrayBufferAPI,后者在第6章介绍过。关于 如何在多个上下文中使用 SharedArrayBuffer,可以参考第 27 章。
任何全局上下文中都有 Atomics 对象,这个对象上暴露了用于执行线程安全操作的一套静态方法, 其中多数方法以一个 TypedArray 实例(一个 SharedArrayBuffer 的引用)作为第一个参数,以相 18 关操作数作为后续参数。

1. 算术及位操作方法

Atomics API 提供了一套简单的方法用以执行就地修改操作。在 ECMA 规范中,这些方法被定义为 AtomicReadModifyWrite 操作。在底层,这些方法都会从 SharedArrayBuffer 中某个位置读取值, 然后执行算术或位操作,最后再把计算结果写回相同的位置。这些操作的原子本质意味着上述读取、修 改、写回操作会按照顺序执行,不会被其他线程中断。
以下代码演示了所有算术方法:

// 创建大小为 1 的缓冲区
let sharedArrayBuffer = new SharedArrayBuffer(1);
// 基于缓冲创建Uint8Array
let typedArray = new Uint8Array(sharedArrayBuffer);
// 所有ArrayBuffer全部初始化为0 console.log(typedArray); // Uint8Array[0]
    const index = 0;
    const increment = 5;
// 对索引0处的值执行原子加5 Atomics.add(typedArray, index, increment);
console.log(typedArray); // Uint8Array[5] // 对索引0处的值执行原子减5
Atomics.sub(typedArray, index, increment); console.log(typedArray); // Uint8Array[0]
  以下代码演示了所有位方法:
// 创建大小为 1 的缓冲区
let sharedArrayBuffer = new SharedArrayBuffer(1);
       // 基于缓冲创建Uint8Array 27 let typedArray = new Uint8Array(sharedArrayBuffer);
// 所有ArrayBuffer全部初始化为0 console.log(typedArray); // Uint8Array[0]
const index = 0;
// 对索引0处的值执行原子或0b1111 Atomics.or(typedArray, index, 0b1111);
console.log(typedArray); // Uint8Array[15] // 对索引0处的值执行原子与0b1111
Atomics.and(typedArray, index, 0b1100); console.log(typedArray); // Uint8Array[12]
// 对索引0处的值执行原子异或0b1111 Atomics.xor(typedArray, index, 0b1111);
console.log(typedArray); // Uint8Array[3] 前面线程不安全的例子可以改写为下面这样:
    const workerScript = `
    self.onmessage = ({data}) => {
    const view = new Uint32Array(data);
// 执行1000000次加操作
for (let i = 0; i < 1E6; ++i) {
// 线程安全的加操作
      Atomics.add(view, 0, 1);
    }
    self.postMessage(null);
  };
`;
const workerScriptBlobUrl = URL.createObjectURL(new Blob([workerScript]));
// 创建容量为 4 的工作线程池 const workers = [];
for (let i = 0; i < 4; ++i) {
    workers.push(new Worker(workerScriptBlobUrl));
  }
// 在最后一个工作线程完成后打印出最终值 let responseCount = 0;
for (const worker of workers) {
    worker.onmessage = () => {
      if (++responseCount == workers.length) {
        console.log(`Final buffer value: ${view[0]}`);
      }
}; }
// 初始化SharedArrayBuffer
const sharedArrayBuffer = new SharedArrayBuffer(4); const view = new Uint32Array(sharedArrayBuffer); view[0] = 1;
// 把 SharedArrayBuffer 发送到每个工作线程 for (const worker of workers) {

Atomics 与 SharedArrayBuffer

指令会在原子读/写完成后才会开始。 除了读写缓冲区的值,Atomics.load()和 Atomics.store()还可以构建“代码围栏”。JavaScript
引擎保证非原子指令可以相对于 load()或 store()本地重排,但这个重排不会侵犯原子读/写的边界。 以下代码演示了这种行为:

   const sharedArrayBuffer = new SharedArrayBuffer(4);
    const view = new Uint32Array(sharedArrayBuffer);
   worker.postMessage(sharedArrayBuffer);
}
//(期待结果为 4000001)
// Final buffer value: 4000001
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值