ECMAScript中Atomics对象的实际用途是什么?

Cri*_*ìna 12 javascript specifications ecmascript-2017

ECMAScript规范定义了原子能对象在部分24.4.

在所有的全球对象中,这对我来说更加模糊,因为在我没有阅读其规范之前我不知道它的存在,而且谷歌也没有多少引用它(或者名称太过通用和一切都被淹没?).

根据其官方定义

Atomics对象提供在共享内存阵列单元上不可分割地(原子地)操作的函数以及允许代理等待和分派原始事件的函数

因此它具有一个对象的形状,有许多方法来处理低级内存并调节对它的访问.它的公共界面也让我想到了.但是这种对象对最终用户的实际使用是什么?为什么公开?是否有一些可用的例子?

谢谢

小智 14

Atomics用于同步共享内存的WebWorkers.它们导致对SharedArrayBuffer的内存访问以线程安全的方式完成.共享内存使多线程更有用,因为:

  • 没有必要复制数据以将其传递给线程
  • 线程可以在不使用事件循环的情况下进行通信
  • 线程可以更快地进行通信

例:

var arr = new SharedArrayBuffer(1024);

// send a reference to the memory to any number of webworkers
workers.forEach(worker => worker.postMessage(arr));

// Normally, simultaneous access to the memory from multiple threads 
// (where at least one access is a write)
// is not safe, but the Atomics methods are thread-safe.
// This adds 2 to element 0 of arr.
Atomics.add(arr, 0, 2)
Run Code Online (Sandbox Code Playgroud)

之前在主要浏览器上启用了SharedArrayBuffer,但在Spectre事件发生后,它被禁用,因为共享内存允许实现纳秒精度计时器,这允许利用幽灵.

为了确保安全,浏览器需要为每个域运行页面单独的进程.Chrome在版本67中开始执行此操作,并在版本68中重新启用了共享内存.


Ars*_*-II 6

如果你有一些复杂的计算,你可能需要WebWorkers,以便你的主脚本可以继续他的工作,而重要的事情是并行完成的.

Atomics解决的问题是WebVorkers如何在彼此之间进行通信(简单,快速和可靠).您可以在这里阅读有关ArrayBuffer,SharedArrayBuffer,Atomics以及如何将它们用于您的好处的信息.

如果出现以下情况,你不应该为此烦恼:

  • 你正在创造一些简单的东西(例如商店,论坛等)

您可能需要它:

  • 你想创造复杂的,内存消耗的东西(例如figma谷歌驱动器)
  • 如果您希望使用WebAssemblywebgl希望优化性能,则需要这样做
  • 如果您想创建一些复杂的Node.js模块,也可能需要它
  • 或者,如果您通过SkypeDiscordElectron创建复杂的应用程序

感谢Pavlo MurSimon Paris为您提供答案!


Dan*_*rom 5

原子操作是“全有或全无”的一组较小的操作。

让我们来看看

let i=0;

i++
Run Code Online (Sandbox Code Playgroud)

i++ 实际上是通过 3 个步骤进行评估的

  1. 读取当前i
  2. 增加i1
  3. 返回旧值

如果有 2 个线程执行相同的操作会发生什么?它们都可以读取相同的值1并在完全相同的时间增加它。

但是这个和Javascript,不是单线程的吗?

是的!JavaScript 确实是单线程,但浏览器/节点今天允许并行使用多个 JavaScript 运行时(工作线程、Web 工作线程)。

Chrome 和 Node(基于 v8)为每个线程创建Isolate,它们都在自己的context.

而罐子的唯一途径share memory是通过ArrayBuffer/SharedArrayBuffer

下一个程序的输出是什么?

使用 node > =10 运行(您可能需要--experimental_worker标志)

node example.js

const { isMainThread, Worker, workerData } = require('worker_threads');

if (isMainThread) {
  // main thread, create shared memory to share between threads
  const shm = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);

  process.on('exit', () => {
    // print final counter
    const res = new Int32Array(shm);
    console.log(res[0]); // expected 5 * 500,000 = 2,500,000
  });
  Array(5).fill(null).map(() => new Worker(__filename, { workerData: shm }));
} else {
  // worker thread, iteratres 500k and doing i++
  const arr = new Int32Array(workerData);
  for (let i = 0; i < 500000; i++) {
    arr[i]++;
  }
}
Run Code Online (Sandbox Code Playgroud)

输出可能是 2,500,000但我们不知道,在大多数情况下它不会是 2.5M,实际上,您两次获得相同输出的机会非常低,作为程序员我们肯定不会就像我们不知道它会如何结束的代码。

这是竞争条件的一个示例,其中 n 个线程相互竞争并且不以任何方式同步。

Atomic操作来了,它允许我们从头到尾进行算术运算。

让我们稍微改变一下程序,现在运行:

const { isMainThread, Worker, workerData } = require('worker_threads');


if (isMainThread) {
    const shm = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
    process.on('exit', () => {
        const res = new Int32Array(shm);
        console.log(res[0]); // expected 5 * 500,000 = 2,500,000
    });
    Array(5).fill(null).map(() => new Worker(__filename, { workerData: shm }));
} else {
    const arr = new Int32Array(workerData);
    for (let i = 0; i < 500000; i++) {
        Atomics.add(arr, 0, 1);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在输出将永远是 2,500,000

奖金,互斥使用原子

有时,我们希望一个操作只能同时被 1 个线程访问,让我们看看下一个类

class Mutex {

    /**
     * 
     * @param {Mutex} mutex 
     * @param {Int32Array} resource 
     * @param {number} onceFlagCell 
     * @param {(done)=>void} cb
     */
    static once(mutex, resource, onceFlagCell, cb) {
        if (Atomics.load(resource, onceFlagCell) === 1) {
            return;
        }
        mutex.lock();
        // maybe someone already flagged it
        if (Atomics.load(resource, onceFlagCell) === 1) {
            mutex.unlock();
            return;
        }
        cb(() => {
            Atomics.store(resource, onceFlagCell, 1);
            mutex.unlock();
        });
    }
    /**
     * 
     * @param {Int32Array} resource 
     * @param {number} cell 
     */
    constructor(resource, cell) {
        this.resource = resource;
        this.cell = cell;
        this.lockAcquired = false;
    }

    /**
     * locks the mutex
     */
    lock() {
        if (this.lockAcquired) {
            console.warn('you already acquired the lock you stupid');
            return;
        }
        const { resource, cell } = this;
        while (true) {
            // lock is already acquired, wait
            if (Atomics.load(resource, cell) > 0) {
                while ('ok' !== Atomics.wait(resource, cell, 0));
            }
            const countOfAcquiresBeforeMe = Atomics.add(resource, cell, 1);
            // someone was faster than me, try again later
            if (countOfAcquiresBeforeMe >= 1) {
                Atomics.sub(resource, cell, 1);
                continue;
            }
            this.lockAcquired = true;
            return;
        }
    }

    /**
     * unlocks the mutex
     */
    unlock() {
        if (!this.lockAcquired) {
            console.warn('you didn\'t acquire the lock you stupid');
            return;
        }
        Atomics.sub(this.resource, this.cell, 1);
        Atomics.notify(this.resource, this.cell, 1);
        this.lockAcquired = false;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您需要SharedArrayBuffer在所有线程之间分配和共享它们,并看到每次只有 1 个线程进入critical section

运行节点 > 10

node --experimental_worker example.js

const { isMainThread, Worker, workerData, threadId } = require('worker_threads');


const { promisify } = require('util');
const doSomethingFakeThatTakesTimeAndShouldBeAtomic = promisify(setTimeout);

if (isMainThread) {
    const shm = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
    Array(5).fill(null).map(() => new Worker(__filename, { workerData: shm }));
} else {
    (async () => {
        const arr = new Int32Array(workerData);
        const mutex = new Mutex(arr, 0);
        mutex.lock();
        console.log(`[${threadId}] ${new Date().toISOString()}`);
        await doSomethingFakeThatTakesTimeAndShouldBeAtomic(1000);
        mutex.unlock();
    })();
}
Run Code Online (Sandbox Code Playgroud)