ECMAScript specification在 24.4 部分中定义原子对象。
在所有全球对象中,这对我来说更加模糊,因为在我没有阅读其规范之前我还不知道它的存在,而Google也没有多少引用它(或者这个名字太通用了,一切都被淹没了?)。
根据其官方定义
Atomics对象提供在共享存储器阵列单元上不可分割地(原子地)操作的功能 以及允许代理等待和分派原始事件的函数
因此它具有一个对象的形状,有许多方法来处理低级内存并调节对它的访问。它的公共界面也让我想到了。但对于最终用户来说,这种对象的实际用途是什么?为什么公开?是否有一些可用的例子?
谢谢
答案 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。
如果发生以下情况,您就不必理会:
如果满足以下条件,您可能会需要它:
WebAssembly
或webgl
,并且想要优化性能,则需要谢谢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个步骤进行评估
i
值i
增加1 如果有2个线程执行相同的操作会发生什么?他们都可以读取相同的值1
并在完全相同的时间将其递增。
是的! 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保证的互斥量,有时添加将无法正确进行。