SharedArrayBuffer in JavaScript
Questo articolo spiega il SharedArrayBuffer
in JavaScript.
Forniremo una spiegazione dettagliata delle basi di SharedArrayBuffer
, come utilizzarlo, casi d'uso specifici e considerazioni sulla sicurezza.
YouTube Video
SharedArrayBuffer in JavaScript
SharedArrayBuffer
è uno strumento potente in JavaScript per condividere la memoria tra più thread. Specialmente in combinazione con i Web Worker, consente l'elaborazione parallela, rendendolo efficace per compiti computazionalmente intensivi e applicazioni che richiedono capacità in tempo reale.
Cos'è SharedArrayBuffer?
SharedArrayBuffer
fornisce un buffer di memoria in JavaScript che consente la condivisione di dati binari tra più thread (principalmente Web Worker). Un ArrayBuffer
normale richiede la copia di dati tra il thread principale e i worker, mentre SharedArrayBuffer
consente la condivisione diretta della memoria senza copie, migliorando significativamente le prestazioni.
Caratteristiche
- Memoria condivisa Permette a più thread di lavorare con lo stesso spazio di memoria.
- Miglioramento delle prestazioni Poiché è possibile evitare la copia, il sovraccarico è ridotto quando si elaborano grandi quantità di dati.
- Sincronizzazione dei thread
Puoi utilizzarlo insieme a
Atomics
per effettuare la sincronizzazione e prevenire conflitti durante l'accesso alla memoria.
Esempio base di utilizzo
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
In questo esempio, creiamo un'area di memoria di 16 byte utilizzando SharedArrayBuffer
e trattiamo quella memoria come un Int32Array
. Questo buffer di memoria può essere condiviso tra più thread.
Utilizzo con i Web Worker
Il vero valore di SharedArrayBuffer
si dimostra quando viene utilizzato in combinazione con i Web Worker. Il seguente codice è un esempio di utilizzo della memoria condivisa tra il thread principale e un worker.
Nel Thread Principale
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]);
Sul Lato del Worker (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};
- In questo esempio, il thread principale crea un buffer condiviso e lo passa al worker. Il worker può accedere a questo buffer per leggere e modificare i valori. In questo modo, i dati possono essere condivisi tra i thread senza copie.
Conferma di aggiornamento bidirezionale
Utilizzando SharedArrayBuffer
, sia il thread principale che i worker possono leggere e scrivere sulla stessa memoria, consentendo la conferma di aggiornamenti bidirezionali. Di seguito è riportato un esempio in cui il thread principale imposta un valore, un worker modifica questo valore e poi il thread principale verifica l'aggiornamento.
Nel Thread Principale
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};
Sul Lato del Worker (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};
- In questo esempio, il thread principale scrive prima il valore
100
, e dopo che il worker lo legge, lo riscrive a200
. Successivamente, il worker notifica il thread principale, che legge di nuovo la memoria condivisa per confermare l’aggiornamento. In questo modo, combinando le notifiche, si rende possibile la conferma degli aggiornamenti bidirezionali.
Sincronizzazione con Atomics
Quando si utilizza la memoria condivisa, è necessario prestare attenzione alle condizioni di competizione sui dati e alle incoerenze. Quando più thread accedono contemporaneamente alla stessa memoria, possono verificarsi conflitti. Per prevenire ciò, JavaScript utilizza l'oggetto Atomics
per la sincronizzazione.
Ad esempio, per incrementare in modo sicuro un contatore con più thread, è possibile utilizzare Atomics
per prevenire conflitti.
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
incrementa atomicamente il valore a un indice specifico e restituisce il nuovo valore. Questa operazione è garantita senza conflitti con altri thread. Atomics.load
è utilizzato anche per leggere in modo sicuro i valori dalla memoria condivisa.
Attesa e notifica tramite Atomics.wait
e Atomics.notify
Quando si utilizza SharedArrayBuffer
, ci sono situazioni in cui un worker deve attendere che una certa condizione sia soddisfatta, e quando un altro worker soddisfa tale condizione, deve notificare il worker in attesa. In questi casi, Atomics.wait
e Atomics.notify
sono utili.
Atomics.wait
blocca un thread finché il valore in un indice specifico della memoria condivisa non cambia, mentre Atomics.notify
avvisa i thread in attesa che possono procedere. Ciò consente un’attesa e una notifica sicure tra più worker. Tuttavia, Atomics.wait
non può essere utilizzato nel thread principale ed è disponibile solo all'interno dei worker.
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};
- Nel thread principale, un
SharedArrayBuffer
viene creato come memoria condivisa e viene convertito in unInt32Array
con un solo elemento. Questo unico spazio per un intero viene utilizzato come segnale per la sincronizzazione tra i worker. Successivamente, vengono creati due worker e a ciascuno viene assegnato un ruolo utilizzando la proprietàname
:waiter
(il ruolo in attesa) enotifier
(il ruolo di notifica). Infine, il buffer condiviso viene passato a entrambi i worker e vengono impostati i gestorionmessage
in modo che i messaggi inviati da ciascun worker possano essere ricevuti.
Sul Lato del Worker (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!
- In questo esempio, il worker
waiter
rimane in stato di attesa utilizzandoAtomics.wait
finché il valore all'indice0
è0
. D'altra parte, quando il workernotifier
cambia il valore a123
conAtomics.store
e chiamaAtomics.notify
, il workerwaiter
riprenderà e potrà ottenere il valore aggiornato. Con questo si può ottenere un'attesa ed una notifica efficiente e sicura tra i thread.
Casi d'uso per SharedArrayBuffer
SharedArrayBuffer
è particolarmente utile per i seguenti casi d'uso:.
- Elaborazione in tempo reale È adatto per applicazioni che richiedono bassa latenza, come l’elaborazione audio/video o i motori di gioco, dove i dati devono essere condivisi istantaneamente tra i thread.
- Calcolo parallelo
Quando si elaborano grandi quantità di dati contemporaneamente con più thread, l’uso di
SharedArrayBuffer
evita la copia della memoria e può migliorare le prestazioni. - Apprendimento automatico Parallelizzando attività come il pre-processamento dei dati e l’inferenza, è possibile ottenere un’elaborazione efficiente.
Considerazioni sulla sicurezza
SharedArrayBuffer
è una funzionalità potente, ma comporta anche rischi per la sicurezza. In particolare, preoccupazioni riguardanti attacchi di tipo side-channel come Spectre
ne hanno temporaneamente sospeso il supporto. Per mitigare questa vulnerabilità, i browser hanno implementato le seguenti misure:.
- Isolamento del sito
I siti che consentono l’uso di
SharedArrayBuffer
verranno eseguiti in un processo completamente isolato dagli altri siti. - Cross-Origin Resource Policy
Per utilizzare
SharedArrayBuffer
, le intestazioniCross-Origin-Opener-Policy
eCross-Origin-Embedder-Policy
devono essere impostate correttamente.
Ad esempio, configurando header come i seguenti, l'uso di SharedArrayBuffer
diventa possibile:.
1Cross-Origin-Opener-Policy: same-origin
2Cross-Origin-Embedder-Policy: require-corp
Questo impedisce alle risorse esterne di interferire con i contenuti correnti e aumenta la sicurezza.
Riepilogo
SharedArrayBuffer
è uno strumento molto potente per la condivisione della memoria tra più thread. È una tecnologia essenziale per migliorare le prestazioni, e i suoi effetti sono particolarmente evidenti nei campi dell'elaborazione in tempo reale e del calcolo parallelo. Tuttavia, comporta anche rischi per la sicurezza, quindi una configurazione e una sincronizzazione corrette sono importanti.
Sfruttando SharedArrayBuffer
, puoi creare applicazioni web più avanzate e ad alte prestazioni.
Puoi seguire l'articolo sopra utilizzando Visual Studio Code sul nostro canale YouTube. Controlla anche il nostro canale YouTube.