SharedMemory と Atomic API について
このエントリは JavaScript stage 0,1,2,3 Advent Calendar 2015 の 19日目の記事です。
ちなみに stage 1 です。
SharedMemory は SharedArrayBuffer と呼ばれる特殊な ArrayBuffer をインタフェースにして提供されます。さらに、 Atomic API というのはこの SharedArrayBuffer を操作するための Atomic という built-in Object です。
ちなみに Node.js v4.0 以降なら --harmony-sharedarraybuffer
と --harmony-atomics
で有効になります。 (Worker がないので無意味ですが)
背景
基本的に WebWorker に代表される Worker は shared nothing、 つまりメモリを共有せず、値をコピーしてpostMessageなどでWorker間で協調しながら値を共有してきました。
このやり方はシンプルですが、コピーせず Worker 間でメモリを共有できたほうが効率的です(もちろん共有するな協調せよという考え方もあります)。
また SharedArrayBuffer を扱うためにはWorker間でのリソースの競合を防ぐ必要があります。これを可能にするために入っているのが Atomic API です。
こちらは SharedArrayBufferで作った配列に対して Atomic な操作を提供します。配列に対してadd/sub/and/or等の操作をする際に他のWorkerが操作していないか確認しロックを取る仕組みになっています。仕様によれば、futexの仕組みを使ってロックを取ると記述があります。
SharedArrayBuffer を共通化する
// main.js
var worker = new Worker("worker.js");
var sab = new SharedArrayBuffer(1024);
worker.onmessage = function(e) {
console.log(e.data);
};
worker.postMessage(sab, [sab]);
// worker
var sab;
onmessage = function (ev) {
sab = ev.data; // data が共有化される。
}
こんな感じで SharedArrayBuffer を共有します。直接 data の配列を変更すると値が修正されます。ただしその場合、 Atomic な操作じゃないので別な worker が変更してしまうと競合が発生する可能性があります。
競合を回避するのが Atomic API です。 Atomic API は下記のようにして使います。
Atomic API
// SharedArrayBuffer and Atomics
// use flag --harmony-sharedarraybuffer --harmony-atomics
var sab = new SharedArrayBuffer(4); // SharedArrayBufferを作る
var uint8Array = new Uint8Array(sab); // Integer Shared Typed Array になる
for (i = 0; i < 4; i++) {
uint8Array[i] = i;
}
uint8Array.forEach((x) => console.log(x)); // 0, 1, 2, 3
Atomics.add(uint8Array, 2, 10); // index 2 に 10 を足す
Atomics.sub(uint8Array, 3, 3); // index 3 から 3 を引く
Atomics.compareExchange(uint8Array, 0, 0, 1); // index 0 が 0 なら 1 にする
uint8Array.forEach((x) => console.log(x)); // 1, 1, 12, 0
console.log(Atomics.load(uint8Array, 2)); // 12 index 2 の値を get する
console.log(Atomics.store(uint8Array, 2, 19)); // 19 index 2 の値を19に書き換えて書き換えた値をgetする
uint8Array.forEach((x) => console.log(x)); // 1, 1, 19, 0