SharedArrayBuffer w JavaScript
Ten artykuł wyjaśnia SharedArrayBuffer
w JavaScript.
Przedstawimy szczegółowe wyjaśnienie podstaw SharedArrayBuffer
, sposobów jego użycia, konkretnych zastosowań oraz kwestii bezpieczeństwa.
YouTube Video
SharedArrayBuffer w JavaScript
SharedArrayBuffer
to potężne narzędzie w JavaScript do współdzielenia pamięci między wieloma wątkami. Szczególnie w połączeniu z Web Workerami umożliwia przetwarzanie równoległe, co czyni go efektywnym dla zadań obciążających obliczeniowo i aplikacji wymagających funkcji w czasie rzeczywistym.
Czym jest SharedArrayBuffer?
SharedArrayBuffer
udostępnia bufor pamięci w JavaScript, który pozwala na współdzielenie danych binarnych między wieloma wątkami (głównie Web Workerami). Zwykły ArrayBuffer
wymaga kopiowania między głównym wątkiem a workerami, ale SharedArrayBuffer
umożliwia bezpośrednie współdzielenie pamięci bez kopiowania, co znacząco poprawia wydajność.
Funkcje
- Pamięć współdzielona Pozwala wielu wątkom pracować w tej samej przestrzeni pamięci.
- Poprawa wydajności Ponieważ kopiowanie może zostać pominięte, narzut zostaje zredukowany podczas przetwarzania dużych ilości danych.
- Synchronizacja wątków
Można jej używać razem z
Atomics
do synchronizacji w celu uniknięcia konfliktów podczas dostępu do pamięci.
Podstawowy Przykład Użytkowania
1// Create a 16-byte shared memory
2const sharedBuffer = new SharedArrayBuffer(16);
3
4// Treat it as an Int32Array
5const sharedArray = new Int32Array(sharedBuffer);
6
7// Set a value
8sharedArray[0] = 42;
9
10console.log(sharedArray[0]); // 42
W tym przykładzie tworzymy 16-bajtowy obszar pamięci za pomocą SharedArrayBuffer
i traktujemy go jako Int32Array
. Ten bufor pamięci może być współdzielony między wieloma wątkami.
Używanie z Web Workerami
Prawdziwa wartość SharedArrayBuffer
ujawnia się, gdy jest używany w połączeniu z Web Workerami. Poniższy kod to przykład użycia współdzielonej pamięci między głównym wątkiem a workerem.
W głównym wątku
1// Create a shared buffer
2const sharedBuffer = new SharedArrayBuffer(16);
3const sharedArray = new Int32Array(sharedBuffer);
4
5// Create a worker
6const worker = new Worker('worker.js');
7
8// Pass the shared buffer to the worker
9worker.postMessage(sharedBuffer);
10
11// Modify the memory
12// Output : Main thread: 100
13sharedArray[0] = 100;
14console.log("Main thread: ", sharedArray[0]);
Po stronie Workera (worker.js
)
1// worker.js
2self.onmessage = function(event) {
3 // Use the received shared buffer
4 const sharedArray = new Int32Array(event.data);
5
6 // Read the contents of the memory
7 // Output : Worker thread: 100
8 console.log("Worker thread: ", sharedArray[0]);
9
10 // Change the value
11 sharedArray[0] = 200;
12};
- W tym przykładzie główny wątek tworzy współdzielony bufor i przekazuje go workerowi. Worker może uzyskiwać dostęp do tego bufora, aby odczytywać i modyfikować wartości. W ten sposób dane mogą być współdzielone między wątkami bez konieczności kopiowania.
Dwukierunkowe potwierdzenie aktualizacji
Korzystając z SharedArrayBuffer
, zarówno główny wątek, jak i workerzy mogą odczytywać i zapisywać te same dane w pamięci, umożliwiając dwukierunkowe potwierdzanie aktualizacji. Poniżej znajduje się przykład, w którym główny wątek ustawia wartość, worker ją zmienia, a następnie główny wątek sprawdza aktualizację.
W głównym wątku
1// Create a shared buffer
2const sharedBuffer = new SharedArrayBuffer(16);
3const sharedArray = new Int32Array(sharedBuffer);
4
5// Create a worker
6const worker = new Worker('worker.js');
7
8// Pass the shared buffer to the worker
9worker.postMessage(sharedBuffer);
10
11// Set initial value
12// Output : Main thread initial: 100
13sharedArray[0] = 100;
14console.log("Main thread initial:", sharedArray[0]);
15
16// Listen for worker confirmation
17worker.onmessage = () => {
18 // Output : Main thread after worker update: 200
19 console.log("Main thread after worker update:", sharedArray[0]);
20};
Po stronie Workera (worker.js
)
1// worker.js
2self.onmessage = function(event) {
3 const sharedArray = new Int32Array(event.data);
4
5 // Read initial value
6 // Output : Worker thread received: 100
7 console.log("Worker thread received:", sharedArray[0]);
8
9 // Update the value
10 sharedArray[0] = 200;
11
12 // Notify main thread
13 self.postMessage("Value updated");
14};
- W tym przykładzie główny wątek najpierw zapisuje wartość
100
, po czym worker odczytuje ją i nadpisuje na200
. Następnie worker powiadamia główny wątek i główny wątek ponownie odczytuje współdzieloną pamięć, aby potwierdzić aktualizację. W ten sposób połączenie powiadomień umożliwia dwukierunkowe potwierdzanie aktualizacji.
Synchronizacja za pomocą Atomics
Podczas używania współdzielonej pamięci należy uważać na wyścigi danych i niespójności. Gdy wiele wątków jednocześnie uzyskuje dostęp do tej samej pamięci, mogą wystąpić konflikty. Aby temu zapobiec, JavaScript wykorzystuje obiekt Atomics
do synchronizacji.
Na przykład, aby bezpiecznie zwiększyć licznik przy użyciu wielu wątków, można użyć Atomics
, aby zapobiec konfliktom.
1const sharedBuffer = new SharedArrayBuffer(16);
2const sharedArray = new Int32Array(sharedBuffer);
3
4// Increment the counter
5Atomics.add(sharedArray, 0, 1);
6
7console.log(Atomics.load(sharedArray, 0)); // 1
Atomics.add
atomowo zwiększa wartość pod określonym indeksem i zwraca nową wartość. Ta operacja jest gwarantowana jako wolna od konfliktów z innymi wątkami. Atomics.load
jest również używane do bezpiecznego odczytu wartości ze współdzielonej pamięci.
Oczekiwanie i powiadamianie z użyciem Atomics.wait
i Atomics.notify
Podczas korzystania z SharedArrayBuffer
zdarzają się sytuacje, gdy worker musi zaczekać na spełnienie określonego warunku, a gdy inny worker warunek ten spełni, musi powiadomić oczekującego workera. W takich przypadkach przydatne są Atomics.wait
i Atomics.notify
.
Atomics.wait
blokuje wątek do czasu zmiany wartości pod określonym indeksem w pamięci współdzielonej, natomiast Atomics.notify
powiadamia oczekujące wątki, że mogą wznowić pracę. Umożliwia to bezpieczne oczekiwanie i powiadamianie pomiędzy wieloma workerami. Jednak Atomics.wait
nie może być używany w głównym wątku i jest dostępny tylko wewnątrz workerów.
1// Create a shared buffer (1 Int32 slot is enough for signaling)
2const sharedBuffer = new SharedArrayBuffer(4);
3const sharedArray = new Int32Array(sharedBuffer);
4
5// Create workers with names
6const waiter = new Worker('worker.js', { name: 'waiter' });
7const notifier = new Worker('worker.js', { name: 'notifier' });
8
9// Pass the shared buffer to both
10waiter.postMessage(sharedBuffer);
11notifier.postMessage(sharedBuffer);
12
13// Listen for messages
14waiter.onmessage = (event) => {
15 console.log(`[Main] Message from waiter:`, event.data);
16};
17notifier.onmessage = (event) => {
18 console.log(`[Main] Message from notifier:`, event.data);
19};
- W głównym wątku tworzony jest
SharedArrayBuffer
jako pamięć współdzielona i przekształcany wInt32Array
z tylko jednym elementem. To pojedyncze miejsce na liczbę całkowitą jest używane jako sygnał do synchronizacji między workerami. Następnie tworzone są dwa workery, z których każdy otrzymuje rolę za pomocą właściwościname
:waiter
(rola oczekującego) inotifier
(rola powiadamiającego). Na końcu wspólny bufor jest przekazywany do obu workerów, a obsługiwaczeonmessage
są ustawiane tak, aby można było odbierać wiadomości wysyłane z każdego workera.
Po stronie Workera (worker.js
)
1// worker.js
2onmessage = (event) => {
3 const sharedArray = new Int32Array(event.data);
4
5 if (self.name === 'waiter') {
6 postMessage('Waiter is waiting...');
7 // Wait until notifier signals index 0
8 Atomics.wait(sharedArray, 0, 0);
9 postMessage('Waiter was notified!');
10 }
11
12 if (self.name === 'notifier') {
13 postMessage('Notifier is preparing...');
14 setTimeout(() => {
15 // Notify waiter after 2 seconds
16 Atomics.store(sharedArray, 0, 1);
17 Atomics.notify(sharedArray, 0, 1);
18 postMessage('Notifier has sent the signal!');
19 }, 2000);
20 }
21};
22// Output
23// [Main] Message from waiter: Waiter is waiting...
24// [Main] Message from notifier: Notifier is preparing...
25// [Main] Message from notifier: Notifier has sent the signal!
26// [Main] Message from waiter: Waiter was notified!
- W tym przykładzie worker
waiter
pozostaje w stanie oczekiwania przy użyciuAtomics.wait
, dopóki wartość pod indeksem0
wynosi0
. Z drugiej strony, gdy workernotifier
zmienia wartość na123
za pomocąAtomics.store
i wywołujeAtomics.notify
, workerwaiter
wznowi pracę i będzie mógł pobrać zaktualizowaną wartość. Dzięki temu można osiągnąć wydajne i bezpieczne oczekiwanie oraz powiadamianie między wątkami.
Przypadki użycia SharedArrayBuffer
SharedArrayBuffer
jest szczególnie przydatny w następujących przypadkach użycia:.
- Przetwarzanie w czasie rzeczywistym Nadaje się do aplikacji wymagających niskich opóźnień, takich jak przetwarzanie dźwięku i obrazu lub silniki gier, gdzie dane muszą być natychmiast współdzielone pomiędzy wątkami.
- Obliczenia równoległe
Podczas przetwarzania dużych ilości danych jednocześnie przez wiele wątków, zastosowanie
SharedArrayBuffer
pozwala uniknąć kopiowania pamięci i może poprawić wydajność. - Uczenie maszynowe Dzięki równoległemu wykonywaniu zadań, takich jak wstępne przetwarzanie danych i wnioskowanie, możliwe staje się efektywne obliczanie.
Zagadnienia bezpieczeństwa
SharedArrayBuffer
to potężna funkcja, ale niesie ze sobą również ryzyko bezpieczeństwa. W szczególności obawy dotyczące ataków pośrednich, takich jak Spectre
, tymczasowo wstrzymały jego obsługę. Aby złagodzić tę lukę, przeglądarki wdrożyły następujące środki:.
- Izolacja stron
Strony umożliwiające korzystanie z
SharedArrayBuffer
będą uruchamiane w procesie całkowicie odizolowanym od innych stron. - Polityka zasobów między źródłami (Cross-Origin Resource Policy)
Aby korzystać z
SharedArrayBuffer
, nagłówkiCross-Origin-Opener-Policy
orazCross-Origin-Embedder-Policy
muszą być prawidłowo ustawione.
Na przykład, ustawiając nagłówki jak poniżej, możliwe staje się użycie SharedArrayBuffer
:.
1Cross-Origin-Opener-Policy: same-origin
2Cross-Origin-Embedder-Policy: require-corp
Zapobiega to ingerencji zasobów zewnętrznych w bieżącą zawartość i zwiększa bezpieczeństwo.
Podsumowanie
SharedArrayBuffer
to bardzo potężne narzędzie do współdzielenia pamięci między wieloma wątkami. Jest to kluczowa technologia poprawiająca wydajność, a jej efekty są szczególnie widoczne w obszarze przetwarzania w czasie rzeczywistym i obliczeń równoległych. Jednak wiąże się to również z zagrożeniami dla bezpieczeństwa, dlatego ważne są prawidłowa konfiguracja i synchronizacja.
Wykorzystując SharedArrayBuffer
, można budować bardziej zaawansowane i wydajne aplikacje internetowe.
Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.