SharedArrayBuffer in JavaScript

SharedArrayBuffer in JavaScript

Dit artikel legt SharedArrayBuffer in JavaScript uit.

We zullen een gedetailleerde uitleg geven over de basisprincipes van SharedArrayBuffer, hoe je het gebruikt, specifieke toepassingsgebieden en veiligheidsaspecten.

YouTube Video

SharedArrayBuffer in JavaScript

SharedArrayBuffer is een krachtig hulpmiddel in JavaScript om geheugen te delen tussen meerdere threads. Met name in combinatie met Web Workers maakt het parallelle verwerking mogelijk, waardoor het effectief is voor rekencapaciteit-intensieve taken en toepassingen die realtime mogelijkheden vereisen.

Wat is SharedArrayBuffer?

SharedArrayBuffer biedt een geheugenbuffer in JavaScript waarmee binaire gegevens gedeeld kunnen worden tussen meerdere threads (vooral Web Workers). Een reguliere ArrayBuffer vereist kopiëren tussen de hoofdthread en workers, maar SharedArrayBuffer maakt direct geheugen delen zonder kopiëren mogelijk, wat de prestaties aanzienlijk verbetert.

Kenmerken

  • Gedeeld geheugen Hierdoor kunnen meerdere threads werken met dezelfde geheugenruimte.
  • Prestatieverbetering Omdat kopiëren kan worden overgeslagen, wordt de overhead verminderd bij het verwerken van grote hoeveelheden data.
  • Thread-synchronisatie Je kunt het samen met Atomics gebruiken om synchronisatie uit te voeren en conflicten bij geheugentoegang te voorkomen.

Basisgebruik Voorbeeld

 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 dit voorbeeld maken we een geheugenruimte van 16 bytes met behulp van SharedArrayBuffer en behandelen die geheugenruimte als een Int32Array. Deze geheugenbuffer kan worden gedeeld tussen meerdere threads.

Gebruik met Web Workers

De ware waarde van SharedArrayBuffer wordt aangetoond wanneer het wordt gebruikt in combinatie met Web Workers. De volgende code is een voorbeeld van het gebruik van gedeeld geheugen tussen de hoofdthread en een worker.

Op de Hoofdthread

 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]);

Aan de Worker-kant (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 dit voorbeeld maakt de hoofdthread een gedeelde buffer en geeft deze door aan de worker. De worker kan deze buffer openen om waarden te lezen en te wijzigen. Op deze manier kunnen gegevens worden gedeeld tussen threads zonder te kopiëren.

Bidirectionele updatebevestiging

Door gebruik te maken van SharedArrayBuffer kunnen zowel de hoofdthread als de workers lezen en schrijven naar hetzelfde geheugen, waardoor bidirectionele updatebevestiging mogelijk wordt. Hieronder volgt een voorbeeld waarbij de hoofdthread een waarde instelt, een worker deze waarde aanpast en de hoofdthread vervolgens controleert op de update.

Op de Hoofdthread

 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};

Aan de Worker-kant (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 dit voorbeeld schrijft de hoofdthread eerst de waarde 100 weg, en nadat de worker deze heeft gelezen, wordt deze overschreven naar 200. Daarna stelt de worker de hoofdthread op de hoogte, en leest de hoofdthread het gedeelde geheugen opnieuw om de update te bevestigen. Op deze manier maakt het combineren van notificaties bidirectionele updatebevestiging mogelijk.

Synchronisatie met Atomics

Bij het gebruik van gedeeld geheugen moet men opletten voor data race-condities en inconsistenties. Wanneer meerdere threads tegelijkertijd toegang hebben tot hetzelfde geheugen, kunnen conflicten optreden. Om dit te voorkomen, gebruikt JavaScript het object Atomics voor synchronisatie.

Bijvoorbeeld, om een teller veilig te verhogen met meerdere threads, kun je Atomics gebruiken om conflicten te voorkomen.

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 verhoogt de waarde op een specifieke index atomaire wijze en retourneert de nieuwe waarde. Deze operatie is gegarandeerd conflictvrij met andere threads. Atomics.load wordt ook gebruikt om waarden veilig uit gedeeld geheugen te lezen.

Wachten en notificatie met Atomics.wait en Atomics.notify

Bij het gebruik van SharedArrayBuffer zijn er situaties waarin een worker moet wachten tot aan een bepaalde voorwaarde is voldaan, en zodra een andere worker aan die voorwaarde voldoet, moet deze de wachtende worker op de hoogte stellen. In zulke gevallen zijn Atomics.wait en Atomics.notify handig.

Atomics.wait blokkeert een thread totdat de waarde op een specifieke index in het gedeelde geheugen verandert, terwijl Atomics.notify wachtende threads laat weten dat ze verder mogen gaan. Hierdoor wordt veilig wachten en notificeren tussen meerdere workers mogelijk. Echter, Atomics.wait kan niet worden gebruikt op de hoofdthread en is alleen beschikbaar binnen workers.

 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};
  • Op de hoofdthread wordt een SharedArrayBuffer aangemaakt als gedeeld geheugen en omgezet naar een Int32Array met slechts één element. Deze enkele integer-slot wordt gebruikt als signaal om te synchroniseren tussen workers. Vervolgens worden er twee workers aangemaakt, en aan elk wordt een rol toegekend via de eigenschap name: waiter (de wachtrol) en notifier (de meldrol). Tot slot wordt de gedeelde buffer doorgegeven aan beide workers, en worden onmessage-handlers ingesteld zodat berichten van elke worker kunnen worden ontvangen.

Aan de Worker-kant (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 dit voorbeeld blijft de waiter-worker in een wachtende toestand met behulp van Atomics.wait zolang de waarde op positie 0 gelijk is aan 0. Wanneer anderzijds de notifier-worker de waarde wijzigt naar 123 met Atomics.store en Atomics.notify aanroept, zal de waiter-worker doorgaan en de bijgewerkte waarde kunnen ophalen. Hiermee kan efficiënt en veilig wachten en melden tussen threads worden gerealiseerd.

Gebruiksscenario's voor SharedArrayBuffer

SharedArrayBuffer is vooral handig voor de volgende gebruiksscenario's:.

  • Realtime verwerking Het is geschikt voor toepassingen die lage latentie vereisen, zoals audio- en videobewerking of game-engines, waarbij data direct tussen threads gedeeld moet worden.
  • Parallelle verwerking Bij het gelijktijdig verwerken van grote hoeveelheden data met meerdere threads, voorkomt het gebruik van SharedArrayBuffer het kopiëren van geheugen en kan het de prestaties verbeteren.
  • Machine learning Door taken als datavoorbewerking en inferentie te paralleliseren, wordt efficiënte berekening mogelijk.

Veiligheidsoverwegingen

SharedArrayBuffer is een krachtige functie, maar het brengt ook beveiligingsrisico's met zich mee. Specifiek hebben zorgen over side-channel aanvallen zoals Spectre tijdelijk geleid tot het stoppen van de ondersteuning. Om deze kwetsbaarheid te verminderen, hebben browsers de volgende maatregelen ingevoerd:.

  • Site-isolatie Sites die het gebruik van SharedArrayBuffer toestaan, draaien in een proces dat volledig geïsoleerd is van andere sites.
  • Cross-Origin Resource Policy Om SharedArrayBuffer te gebruiken, moeten de headers Cross-Origin-Opener-Policy en Cross-Origin-Embedder-Policy correct ingesteld zijn.

Bijvoorbeeld, door headers als de volgende in te stellen, wordt het gebruik van SharedArrayBuffer mogelijk:.

1Cross-Origin-Opener-Policy: same-origin
2Cross-Origin-Embedder-Policy: require-corp

Dit voorkomt dat externe bronnen interfereren met de huidige inhoud en verhoogt de beveiliging.

Samenvatting

SharedArrayBuffer is een zeer krachtig hulpmiddel voor het delen van geheugen tussen meerdere threads. Het is een essentiële technologie voor het verbeteren van prestaties, en de effecten ervan zijn vooral duidelijk in de gebieden van realtime verwerking en parallell rekenen. Het brengt echter ook veiligheidsrisico's met zich mee, dus correcte configuratie en synchronisatie zijn belangrijk.

Door gebruik te maken van SharedArrayBuffer, kun je meer geavanceerde en beter presterende webapplicaties bouwen.

Je kunt het bovenstaande artikel volgen met Visual Studio Code op ons YouTube-kanaal. Bekijk ook het YouTube-kanaal.

YouTube Video