JavaScript アドバンス

JavaScript Atomics

1. Atomics オブジェクトとは?

Atomics オブジェクトは、共有メモリ(Shared Memory)に対して低レベルなアトミック(原子状)操作を提供します。
これは、SharedArrayBuffer および Typed Array(タイプド配列) と組み合わせて、メインスレッドと Worker の間でデータを共有するために使用されます。

複数のスレッド(例えばメインスレッドと1つ以上の Worker)が同じデータにアクセスすると、レースコンディション(競合状態)が発生する可能性があります。Atomics は、以下の特徴を持つ操作を提供することで、これらのレースコンディションを回避するのに役立ちます。

  • 共有されたタイプド配列に対して動作する
  • アトミックに実行される(途中でインタラプトされないことが保証される)
  • エレメントの以前の値を返す

Atomics オブジェクトは、Math のようなグローバルオブジェクトであり、Atomics.load()Atomics.store()Atomics.add() などの静的メソッドを持ちます。

Atomics は高度な機能です。主に以下を扱う際に使用されます:

  • Web Worker
  • Worker Thread
  • 共有メモリ(Shared Memory)

2. 使用要件

Atomics を使用するには、以下の要素が必要です:

  • SharedArrayBuffer
  • 共有バッファを使用するタイプド配列(例:Int32Array
  • 同じバッファを共有する1つ以上の Worker(またはスレッド)

セキュリティ上の注意: Web上で SharedArrayBuffer を有効にするには、ブラウザ側で特定のヘッダー(COOP/COEPなど)の設定が必要になる場合があります。

3. Atomics の基本的な使い方

3.1 共有タイプド配列の作成例

共有バッファと、共有された整数配列を作成します:

const buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);

// 4つの要素を持つ共有整数配列
const sharedArray = new Int32Array(buffer);

// 配列を初期化する
sharedArray[0] = 10;
sharedArray[1] = 20;
sharedArray[2] = 30;
sharedArray[3] = 40;

4. アトミックな読み取りと書き込み

共有タイプド配列内のエレメントを読み取るには Atomics.load() を、書き込むには Atomics.store() を使用します。

4.1 Atomics.load() と Atomics.store() の例

var buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
var sharedArray = new Int32Array(buffer);

// 値をアトミックに保存する
Atomics.store(sharedArray, 0, 123);

// 値をアトミックにロードする
var value = Atomics.load(sharedArray, 0);

console.log("値:", value); // 値: 123
  • Atomics.store() は、保存した値を返します。
  • Atomics.load() は、そのエレメントの現在の値を返します。

5. アトミックな加算と減算

Atomics.add() および Atomics.sub() は、値を変更し、変更前の古い(Old)値を返します。

5.1 Atomics.add() の例

var buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
var sharedArray = new Int32Array(buffer);

sharedArray[0] = 5;

// 値を 3 加算し、古い値を返す
var oldValue = Atomics.add(sharedArray, 0, 3);

console.log("古い値:", oldValue);   // 古い値: 5
console.log("新しい値:", sharedArray[0]); // 新しい値: 8

5.2 Atomics.sub() の例

var buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
var sharedArray = new Int32Array(buffer);

sharedArray[0] = 10;

// 値を 4 減算し、古い値を返す
var oldValue = Atomics.sub(sharedArray, 0, 4);

console.log("古い値:", oldValue);   // 古い値: 10
console.log("新しい値:", sharedArray[0]); // 新しい値: 6

6. アトミックな交換(Exchange)と比較交換

Atomics.exchange() は新しい値を設定し、古い値を返します。Atomics.compareExchange() は、現在の値が期待される値と等しい場合にのみ新しい値を設定します。

6.1 Atomics.exchange() の例

var buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
var sharedArray = new Int32Array(buffer);

sharedArray[0] = 1;

// 値を 99 に置き換え、古い値を返す
var oldValue = Atomics.exchange(sharedArray, 0, 99);

console.log("古い値:", oldValue);   // 古い値: 1
console.log("新しい値:", sharedArray[0]); // 新しい値: 99

6.2 Atomics.compareExchange() の例

var buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
var sharedArray = new Int32Array(buffer);

sharedArray[0] = 10;

// 現在の値が 10 の場合のみ、10 を 20 に置き換える
var oldValue = Atomics.compareExchange(sharedArray, 0, 10, 20);

console.log("古い値:", oldValue);   // 古い値: 10
console.log("新しい値:", sharedArray[0]); // 新しい値: 20

// 期待される値(10)と現在の値(20)が一致しないため、変更されない
oldValue = Atomics.compareExchange(sharedArray, 0, 10, 30);

console.log("古い値:", oldValue);   // 古い値: 20
console.log("新しい値:", sharedArray[0]); // 新しい値: 20

7. Atomics.wait() と Atomics.notify()

Atomics.wait()(Worker内)は、特定のポジションの値が変化するまでスレッドをスリープ状態にできます。そして Atomics.notify() は、スリープしている1つ以上のスレッドをウェイクアップ(再開)させます。

       重要: ブラウザ環境において Atomics.wait() は、Worker コンテキスト内でのみ使用可能です(メインスレッドでは使用できません)。

7.1 値を待機する Worker の例

メインスレッド (main.js):

// main.js
var buffer = new SharedArrayBuffer(4);
var sharedArray = new Int32Array(buffer);

sharedArray[0] = 0;

var worker = new Worker("worker.js");

// 共有バッファを Worker に送信する
worker.postMessage(buffer);

// 何らかの処理をシミュレートした後、値を設定して Worker に通知する
setTimeout(function() {
  Atomics.store(sharedArray, 0, 1);
  Atomics.notify(sharedArray, 0, 1); // 1つの Worker をウェイクアップさせる
}, 1000);

Worker (worker.js):

// worker.js
onmessage = function(e) {
  var buffer = e.data;
  var sharedArray = new Int32Array(buffer);

  console.log("Worker 待機中...");
  // 値が 0 である間は待機する
  var result = Atomics.wait(sharedArray, 0, 0); 

  console.log("Worker が再開されました。結果 =", result);
  console.log("新しい値:", Atomics.load(sharedArray, 0));
};