javascript基础学习系列四百八十九:原子操作基础

任何全局上下文中都有 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]
JavaScript API 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) {

指令会在原子读/写完成后才会开始。 19 除了读写缓冲区的值,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

2. 原子读和写

浏览器的 JavaScript 编译器和 CPU 架构本身都有权限重排指令以提升程序执行效率。正常情况下, JavaScript 的单线程环境是可以随时进行这种优化的。但多线程下的指令重排可能导致资源争用,而且 极难排错。
Atomics API 通过两种主要方式解决了这个问题。
 所有原子指令相互之间的顺序永远不会重排。
 使用原子读或原子写保证所有指令(包括原子和非原子指令)都不会相对原子读/写重新排序。
这意味着位于原子读/写之前的所有指令会在原子读/写发生前完成,而位于原子读/写之后的所有

console.log(Atomics.load(view, 0)); // 1 22
// 执行非原子写 view[0] = 1;
// 非原子写可以保证在这个读操作之前完成,因此这里一定会读到1 // 执行原子写
Atomics.store(view, 0, 2);
// 非原子读可以保证在原子写完成后发生,因此这里一定会读到2 console.log(view[0]); // 2
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值