Skip to main content

Що робить SharedArrayBuffer

SharedArrayBuffer це спеціальний тип буфера, який містить сирі двійкові дані і може бути розділений між різними потоками, наприклад між Web Worker і головним потоком. Він дозволяє одночасно читати й писати в ту саму пам'ять і не копіює дані при передачі: передається посилання на ту саму ділянку пам'яті.

Теорія

TL;DR

  • SharedArrayBuffer тримає сирі байти і доступний одразу кільком потокам.
  • При postMessage він не копіюється і не від'єднується, обидва потоки бачать ту саму пам'ять.
  • Читати й писати треба через типізований масив, наприклад Int32Array.
  • Одночасний доступ означає ризик гонок даних, тому потрібен Atomics.
  • Atomics.wait() і Atomics.notify() дають справжню синхронізацію потоків.
  • Працює у Web Workers і в Node.js (worker_threads).
  • У браузері доступний лише в cross-origin isolated контексті (COOP + COEP).

Швидкий приклад

javascript
// A shared buffer of 4 bytes, which is exactly one Int32 const sharedBuffer = new SharedArrayBuffer(4); const sharedArray = new Int32Array(sharedBuffer); sharedArray[0] = 0; Atomics.add(sharedArray, 0, 1); // atomic increment, safe across threads console.log(Atomics.load(sharedArray, 0)); // 1

Відмінності від звичайного ArrayBuffer

ВластивістьArrayBufferSharedArrayBuffer
Хто використовуєОдин потікКілька потоків
Передача у воркерКопіюється або передається через transfer і стає недоступним у вихідному потоціРозділяється, обидва потоки бачать ту саму пам'ять
Чи можна використовувати AtomicsНіТак, для безпечної синхронізації
ПотокобезпечністьНе потрібнаПотрібна, через Atomics

Спільний буфер між потоками

Головний потік (main.js):

javascript
// Create a shared buffer of 4 bytes (Int32 = 4 bytes) const sharedBuffer = new SharedArrayBuffer(4); // Wrap it in a typed array const sharedArray = new Int32Array(sharedBuffer); sharedArray[0] = 0; // initial value // Create a worker and hand it the shared buffer const worker = new Worker('worker.js'); worker.postMessage(sharedBuffer); // Watch for changes setInterval(() => { console.log('Main thread value:', sharedArray[0]); }, 1000);

Воркер (worker.js):

javascript
onmessage = (e) => { const sharedArray = new Int32Array(e.data); // Increase the value every 500 ms setInterval(() => { Atomics.add(sharedArray, 0, 1); // atomic increment }, 500); };

Результат у консолі:

text
Main thread value: 0 Main thread value: 2 Main thread value: 4 Main thread value: 6 ...

SharedArrayBuffer забезпечує спільну пам'ять, а Atomics гарантує потокобезпечність, тобто відсутність гонок даних.

Навіщо потрібні Atomics

Оскільки кілька потоків можуть одночасно читати й записувати ту саму комірку пам'яті, без синхронізації виникають гонки (race conditions). Для цього існує об'єкт Atomics з атомарними операціями:

МетодЩо робить
Atomics.add(typedArray, index, value)Атомарно додає значення
Atomics.sub()Атомарно віднімає
Atomics.and() / or() / xor()Побітові атомарні операції
Atomics.load() / Atomics.store()Безпечне читання та запис
Atomics.exchange()Замінює значення і повертає старе
Atomics.compareExchange()Замінює, якщо поточне значення збігається з очікуваним
Atomics.wait() / Atomics.notify()Дозволяє «приспати» потік, доки значення не зміниться (лише у воркерах)

Синхронізація через Atomics.wait() і Atomics.notify():

javascript
// main.js const sharedBuffer = new SharedArrayBuffer(4); const sharedArray = new Int32Array(sharedBuffer); const worker = new Worker('worker.js'); worker.postMessage(sharedBuffer); // Wake the worker up after 2 seconds setTimeout(() => { console.log('Main: notify worker'); Atomics.store(sharedArray, 0, 1); Atomics.notify(sharedArray, 0, 1); }, 2000);
javascript
// worker.js onmessage = (e) => { const sharedArray = new Int32Array(e.data); console.log('Worker: waiting...'); Atomics.wait(sharedArray, 0, 0); // blocks until the value changes console.log('Worker: woken up, value:', sharedArray[0]); };

Тут воркер буквально «спить», доки головний потік його не «розбудить». Це дає справжню координацію між потоками JavaScript.

Де використовується

СценарійЗастосування
Web WorkersПередача великих даних без копіювання
Багатопотокові обчисленняПаралельна обробка матриць, графіки, фізики
Ігри та симуляціїОновлення стану світу в кількох потоках
Machine learning та WASMTensorFlow.js, WebAssembly, OpenCV використовують SharedArrayBuffer
Обробка відео та звукуСпільне використання буферів між потоками
Шина обміну між воркерамиМиттєва передача даних без серіалізації у JSON

Безпека і Cross-Origin Isolation

Через ризики вразливостей класу Spectre браузери вмикають SharedArrayBuffer лише в ізольованих контекстах (cross-origin isolation). Щоб він працював, сервер має віддавати заголовки:

text
Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp

І всі ресурси (скрипти, воркери, зображення) мають бути сумісні з цим режимом.

Для dev-сервера, наприклад Vite або Express, це виглядає так:

javascript
app.use((req, res, next) => { res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); next(); });

Тепер браузер дозволить використовувати SharedArrayBuffer.

Підсумок:

ВластивістьОпис
Що робитьРозділяє пам'ять між потоками без копіювання
Де працюєУ Web Workers і в Node.js (worker_threads)
Що потребуєAtomics для синхронізації
БезпекаЛише при увімкнених COOP і COEP
Типові сценаріїПаралельні обчислення, кеш, рендеринг, WebAssembly
Аналог у C та C++Спільна пам'ять (shared memory) між потоками

Типові помилки

  • Пишуть у спільний масив звичайним присвоєнням у кількох потоках. sharedArray[0]++ не атомарний, і два потоки загублять інкремент. Використовуйте Atomics.add().
  • Забувають про заголовки COOP і COEP. Без cross-origin isolation конструктор SharedArrayBuffer просто недоступний, і код падає з ReferenceError.
  • Викликають Atomics.wait() у головному потоці. Він блокує потік, тому в основному потоці браузера заборонений; це лише для воркерів.
  • Очікують, що postMessage скопіює буфер. Тут передається саме спільна пам'ять, тому зміни в одному потоці миттєво видно в іншому.
  • Використовують SharedArrayBuffer там, де вистачає postMessage. Для рідкісного обміну невеликими повідомленнями структурне клонування простіше і безпечніше.
  • Ігнорують вирівнювання типів. Atomics працює лише з цілочисельними типізованими масивами, наприклад Int32Array чи BigInt64Array, але не з Float64Array.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.