Що робить 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).
Швидкий приклад
// 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
| Властивість | ArrayBuffer | SharedArrayBuffer |
|---|---|---|
| Хто використовує | Один потік | Кілька потоків |
| Передача у воркер | Копіюється або передається через transfer і стає недоступним у вихідному потоці | Розділяється, обидва потоки бачать ту саму пам'ять |
Чи можна використовувати Atomics | Ні | Так, для безпечної синхронізації |
| Потокобезпечність | Не потрібна | Потрібна, через Atomics |
Спільний буфер між потоками
Головний потік (main.js):
// 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):
onmessage = (e) => {
const sharedArray = new Int32Array(e.data);
// Increase the value every 500 ms
setInterval(() => {
Atomics.add(sharedArray, 0, 1); // atomic increment
}, 500);
};Результат у консолі:
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():
// 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);// 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 та WASM | TensorFlow.js, WebAssembly, OpenCV використовують SharedArrayBuffer |
| Обробка відео та звуку | Спільне використання буферів між потоками |
| Шина обміну між воркерами | Миттєва передача даних без серіалізації у JSON |
Безпека і Cross-Origin Isolation
Через ризики вразливостей класу Spectre браузери вмикають SharedArrayBuffer лише в ізольованих контекстах (cross-origin isolation). Щоб він працював, сервер має віддавати заголовки:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corpІ всі ресурси (скрипти, воркери, зображення) мають бути сумісні з цим режимом.
Для dev-сервера, наприклад Vite або Express, це виглядає так:
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.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.