Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що робить SharedArrayBuffer». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`SharedArrayBuffer` це буфер сирих двійкових даних, який можна розділити між кількома потоками (наприклад, головним потоком і Web Worker), так що всі вони бачать одну й ту саму ділянку пам'яті.** На відміну від `ArrayBuffer`, при передачі у воркер він не копіюється і не «від'єднується»: передається посилання на ту саму пам'ять, тому обидва потоки можуть читати і писати одночасно. Саме тому потрібен `Atomics`: атомарні операції (`add`, `load`, `store`, `compareExchange`, `wait`, `notify`) гарантують відсутність гонок даних і дають примітиви синхронізації. Це основа реального паралелізму в JavaScript: багатопотокові обчислення, обробка відео та звуку, WebAssembly, ігрові рушії. У браузері `SharedArrayBuffer` доступний лише в cross-origin isolated контексті, тобто із заголовками COOP і COEP, через вразливості класу Spectre. ```javascript const sharedBuffer = new SharedArrayBuffer(4); // 4 bytes = one Int32 const sharedArray = new Int32Array(sharedBuffer); Atomics.add(sharedArray, 0, 1); // race-free increment ``` **Ключове:** `SharedArrayBuffer` дає спільну пам'ять між потоками без копіювання, а `Atomics` робить доступ до неї коректним.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`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` | Властивість | `ArrayBuffer` | `SharedArrayBuffer` | | --- | --- | --- | | Хто використовує | Один потік | Кілька потоків | | Передача у воркер | Копіюється або передається через `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 та WASM** | TensorFlow.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`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.