SharedArrayBuffer

SharedArrayBuffer オブジェクトは、一般的な、生のバイナリーデータバッファーを表すために使用されます。ArrayBuffer オブジェクトと似ていますが、こちらは共有メモリー上にビューを生成するために使用されます。SharedArrayBuffer移譲可能オブジェクトではありません。この点では ArrayBuffer が移譲可能であるのとは異なります。

解説

SharedArrayBuffer オブジェクトを使用して、クラスター内のあるエージェントから別のエージェント (エージェントとは、ウェブページのメインプログラムまたはそのウェブワーカーのひとつ) へ、SharedArrayBuffer オブジェクトを使用してメモリーを共有するために、postMessage構造化複製を使用します。

構造化複製アルゴリズムは SharedArrayBuffer と、SharedArrayBuffer にマッピングされた型付き配列を受け入れます。どちらの場合も SharedArrayBuffer オブジェクトは受信者に転送されて、受信側のエージェントで新たなプライベートの SharedArrayBuffer オブジェクトになります(ArrayBuffer と同じように)。しかし、2 つの SharedArrayBuffer オブジェクトから参照される共有データブロックは同一のデータブロックであり、あるエージェントによるブロックへの副作用は、結果的に他方のエージェントからも見えます。

js
const sab = new SharedArrayBuffer(1024);
worker.postMessage(sab);

共有メモリーは、ワーカー内でもメインスレッド内でも同時に生成や更新ができます。システム (CPU、OS、ブラウザー) によっては、変更がすべてのコンテキストに通知されるまでに少々時間がかかります。同期するためには、不可分操作が必要です。

SharedArrayBuffer オブジェクトは、以下のように一部のウェブ API で使用されています。

セキュリティの要件

共有メモリーと高解像度タイマーは、Spectre の対策として 2018 年の初めに事実上無効化されました。 2020 年には、共有メモリーを再び有効にするために、新しい安全なアプローチが標準化されました。

基本的な要件として、文書が安全なコンテキストにある必要があります。

最上位の文書では、サイトにオリジン間の分離性を持たせるため、次の 2 つのヘッダーを設定する必要があります。

  • Cross-Origin-Opener-Policysame-origin の値を指定すること(オリジンを攻撃者から守るため)
  • Cross-Origin-Embedder-Policyrequire-corp または credentialless の値を指定すること(被害者を自分のオリジンから守るため)
http
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

オリジン間の分離が成功したかどうかは、ウィンドウとワーカーのコンテキストで利用できる crossOriginIsolated (en-US) プロパティを使って確認することができます。

js
const myWorker = new Worker("worker.js");

if (crossOriginIsolated) {
  const buffer = new SharedArrayBuffer(16);
  myWorker.postMessage(buffer);
} else {
  const buffer = new ArrayBuffer(16);
  myWorker.postMessage(buffer);
}

これらの 2 つのヘッダーが設定されていた場合、 postMessage()SharedArrayBuffer オブジェクトに例外を発生させなくなり、従ってスレッド間での共有メモリーが利用できるようになります。

入れ子の文書と専用ワーカーは同様に、 Cross-Origin-Embedder-Policy ヘッダーを同じ値で設定する必要があります。同一オリジンの入れ子の文書とサブリソースについては、これ以上の変更は必要ありません。同一サイト(ただし別オリジン)の入れ子の文書とサブリソースは、 Cross-Origin-Resource-Policy ヘッダーを same-site という値で設定する必要があります。そして、同様に別オリジン(かつ別サイト)のものは、 cross-origin を値として同じヘッダーを設定する必要があります。 Cross-Origin-Resource-Policy ヘッダーを same-origin 以外の値に設定すると、Spectre などの潜在的な攻撃にリソースがさらされることになることに注意してください。

Cross-Origin-Opener-Policy ヘッダーはポップアップへの参照を保持するための能力を制限していることに注意してください。2 つの最上位のウィンドウコンテキスト間の直接アクセスは、基本的に、同一オリジンであり、同じ 2 つの値を持つヘッダーを運んでいる場合にのみ動作するようになっています。

API の利用可能性

上記のセキュリティ対策の有無により、各種メモリー共有 API の利用可能性は異なります。

  • Atomics オブジェクトは常に利用できます。
  • SharedArrayBuffer オブジェクトは原則として常に利用できますが、残念ながら、ウェブコンテンツとの互換性のために、上記の 2 つのヘッダーが設定されていない限り、グローバルオブジェクトのコンストラクターは隠されます。この制限は将来的に取り除かれることが期待されています。WebAssembly.Memory はまだインスタンスを取得するために使用することができます。
  • 上記の 2 つのヘッダーが設定されていない限り、さまざまな postMessage() API が SharedArrayBuffer オブジェクトに対して例外を発生することになります。これらが設定された場合は、Window オブジェクトと専用ワーカーの postMessage() が機能し、メモリーを共有できるようになります。

WebAssembly の共有メモリー

WebAssembly.Memory オブジェクトは、コンストラクターの shared フラグで作成することができます。このフラグを true に設定すると、構築されたメモリーオブジェクトは SharedArrayBuffer と同様に postMessage() を通じてワーカー間で共有でき、メモリーオブジェクトの背後となる bufferSharedArrayBuffer となります。したがって、ワーカー間で SharedArrayBuffer を共有するための上記の要件は、WebAssembly.Memory.Buffer を共有する場合にも当てはまります。

WebAssembly Threads の提案では、新しい不可分命令の集合も定義されています。SharedArrayBuffer とそのメソッドが無条件に有効であるように(そしてスレッド間の共有のみが新しいヘッダー上で制限されます)、WebAssembly の不可分命令も無条件に許可されます。

SharedArrayBuffer の成長

SharedArrayBuffer オブジェクトは SharedArrayBuffer() コンストラクターを呼び出す際に maxByteLength オプションを含めることで成長可能にすることができます。また、SharedArrayBuffergrowable (en-US) および maxByteLength (en-US) プロパティを参照すれば、そのサイズが成長可能かどうかを調べることが可能であり、最大サイズは何であるかがわかります。成長可能な SharedArrayBuffer には grow() (en-US) を呼び出して新しいサイズを割り当てることができます。新しいバイトは 0 に初期化されます。

これらの機能により、SharedArrayBuffer をより効率的に成長させることができます。そうしないと、新しいサイズのバッファーコピーを作成しなければなりません。また、この点において、JavaScript は WebAssembly と同等になります(WASM のリニアメモリーは WebAssembly.Memory.prototype.grow() でサイズを変更することができます)。

セキュリティ上の理由から、SharedArrayBuffer はサイズを縮小することはできませんが、大きくすることはできます。

コンストラクター

SharedArrayBuffer()

新しい SharedArrayBuffer オブジェクトを生成します。

インスタンスプロパティ

これらのプロパティは SharedArrayBuffer.prototype で定義されており、すべての SharedArrayBuffer インスタンスで共有されます。

SharedArrayBuffer.prototype.byteLength

配列のサイズ(バイト単位)。これは配列の構築時に設定され、SharedArrayBuffer が成長可能である場合にのみ SharedArrayBuffer.prototype.grow() (en-US) メソッドを使用して変更することができます。

SharedArrayBuffer.prototype.maxByteLength (en-US) Experimental

読み取り専用で、SharedArrayBuffer が成長できる最大長をバイト数で指定します。これは配列が構築される際に設定され、変更することはできません。

SharedArrayBuffer.prototype.growable (en-US) Experimental

読み取り専用です。SharedArrayBuffer が成長可能な場合は true を、そうでない場合は false を返します。

SharedArrayBuffer.prototype.constructor

インスタンスオブジェクトを作成したコンストラクター関数です。SharedArrayBuffer`インスタンスの場合、初期値はSharedArrayBufferコンストラクターです。

SharedArrayBuffer.prototype[@@toStringTag]

@@toStringTag プロパティの初期値は文字列 "SharedArrayBuffer" です。このプロパティは Object.prototype.toString() で使用されます。

インスタンスメソッド

SharedArrayBuffer.prototype.slice()

新しい SharedArrayBuffer を作成し、その中身をこの SharedArrayBufferbegin の位置から end の位置の一つ手前までのバイトをコピーして返します。 begin または end が負の数の場合は、配列の先頭からではなく末尾からの位置で参照します。

SharedArrayBuffer.prototype.grow() (en-US) Experimental

SharedArrayBuffer を指定したサイズ(バイト単位)まで成長させる。

新しい SharedArrayBuffer の生成

js
const sab = new SharedArrayBuffer(1024);

SharedArrayBuffer の分割

js
sab.slice(); // SharedArrayBuffer { byteLength: 1024 }
sab.slice(2); // SharedArrayBuffer { byteLength: 1022 }
sab.slice(-2); // SharedArrayBuffer { byteLength: 2 }
sab.slice(0, 1); // SharedArrayBuffer { byteLength: 1 }

WebGL バッファー内での使用

js
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, sab, gl.STATIC_DRAW);

仕様書

Specification
ECMAScript Language Specification
# sec-sharedarraybuffer-objects

ブラウザーの互換性

BCD tables only load in the browser

関連情報