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

时间:2017-08-24 21:06:18

标签: javascript specifications ecmascript-2017

ECMAScript specification 24.4 部分中定义原子对象

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

根据其官方定义

  

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

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

谢谢

5 个答案:

答案 0 :(得分:12)

原子用于同步共享内存的WebWorker。它们导致以线程安全的方式对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)

之前在主要的浏览器上启用了SharedArrayBuffer,但在Spectre incident之后禁用了它,因为共享内存允许实现纳秒级精度的计时器,从而可以利用光谱。

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

答案 1 :(得分:6)

如果您有一些复杂的计算,则可能需要here,以便您的主脚本可以在并行完成繁重的工作时继续他的工作。

Atomics解决的问题是WebVorker之间的通讯方式(轻松,快速和可靠)。您可以了解ArrayBuffer,SharedArrayBuffer,Atomics以及如何将其用于收益figma

如果发生以下情况,您就不必理会:

  • 您正在创建简单的内容(例如商店,论坛等)

如果满足以下条件,您可能会需要它:

  • 您要创建一些复杂且消耗内存的内容(例如google driveElectron
  • 如果您想使用WebAssemblywebgl,并且想要优化性能,则需要
  • 如果您想创建一些复杂的Node.js模块,也可能需要它
  • 或者如果您是通过SkypeDiscord之类的Pavlo Mur创建复杂的应用,则

谢谢Simon Paris和{{3}}的答案!

答案 2 :(得分:4)

除了Arseniy-II和Simon Paris所说的外,当您将JavaScript引擎嵌入某些主机应用程序(以在其中启用脚本)时,Atomics也会很方便。然后,人们可以同时从不同的并发线程直接从JS和C / C ++或您的宿主应用程序编写的任何语言直接访问共享内存,而无需使用JavaScript API即可在C / C ++ / OtherLanguage方面进行访问。

答案 3 :(得分:3)

原子操作是“什么都没有”的一组较小的操作。

让我们看看

let i=0;

i++

i++实际上需要3个步骤进行评估

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

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

但这是Java语言,不是单线程吗?

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

Chrome和Node(基于v8)为每个线程创建Isolate,它们都在各自的context中运行。

share memory的唯一方法是通过ArrayBuffer / SharedArrayBuffer

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

在节点> = 10上运行

node --experimental_worker example.js

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++) {
    arr[i]++;
  }
}

输出可能是 2,500,000,但我们不知道,在大多数情况下,输出不会是250万,实际上,您获得两次相同的输出非常低,而且作为程序员,我们当然不喜欢我们不知道它将如何结束的代码。

这是争用条件的一个示例,其中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);
    }
}

现在输出将始终为2,500,000

奖金,使用原子的互斥体

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

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;
    }
}

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

在节点> 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();
    })();
}

答案 4 :(得分:1)

我已经使用Web Worker和SharedArrayBuffer编写了一个脚本,以演示Atomics的用法:

<!DOCTYPE html><html><head></head><body><script>
   var arr = new SharedArrayBuffer(256);
   new Int16Array(arr)[0]=0;
   var workers=[];
   for (let i=0; i<1000; i++) workers.push(new Worker('worker.js'));
   workers.forEach(w => w.postMessage(new Int16Array(arr)));
</script></body></html>

然后使用单独的文件worker.js:

// worker.js
onmessage = function(e) {
    e.data[0]++;                 // last line is 981 only? wth?!
    //Atomics.add(e.data,0,1);   // last line is exactly 1000. right...
    console.log(e.data[0]);
}

如您所见,如果没有Atomics保证的互斥量,有时添加将无法正确进行。