De `SharedArrayBuffer` in TypeScript
Dit artikel legt de SharedArrayBuffer in TypeScript uit.
We leggen SharedArrayBuffer in TypeScript uit aan de hand van praktische voorbeelden.
YouTube Video
De SharedArrayBuffer in TypeScript
SharedArrayBuffer is een mechanisme om dezelfde geheugenruimte te delen tussen meerdere threads, zoals Web Workers. Door het te combineren met Atomics kun je dataraces beheren en laag-latent gedeeld-geheugenoperaties uitvoeren.
Vereisten en opmerkingen
Bij gebruik van SharedArrayBuffer in de browser zijn COOP- en COEP-headers vereist om te voldoen aan beveiligingseisen, bekend als cross-origin isolation. In Node.js kan gedeeld geheugen relatief eenvoudig worden beheerd met behulp van worker_threads.
Basisconcepten
SharedArrayBuffer is een object dat een opeenvolging van bytes van vaste lengte voorstelt; je kunt getallen lezen en schrijven via TypedArray en vergelijkbare views. Eenvoudige lees/schrijfoperaties zijn niet gesynchroniseerd, dus gebruik je de Atomics API om atomaire operaties te garanderen en de wait- en notify-mechanismen voor coördinatie.
Eenvoudige teller (browserversie)
In dit voorbeeld maakt de hoofdthread een SharedArrayBuffer aan en geeft die door aan een Web Worker, waarbij beiden een gedeelde teller verhogen. Dit demonstreert het minimale patroon: atomaire optelling met Atomics.add en lezen met Atomics.load.
main.ts (browserzijde)
Dit voorbeeld laat zien hoe de hoofdthread een SharedArrayBuffer aanmaakt en deelt met een Worker voor multi-threaded toegang.
1// main.ts
2// Create a 4-byte (Int32) shared buffer for one counter
3const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 1);
4const counter = new Int32Array(sab); // view over the buffer
5
6// Create worker and transfer the SharedArrayBuffer
7const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
8worker.postMessage({ sab });
9
10// Increase counter in main thread every 200ms
11setInterval(() => {
12 const newVal = Atomics.add(counter, 0, 1) + 1; // Atomically increment
13 console.log('Main incremented ->', newVal);
14}, 200);
15
16// Listen for messages (optional)
17worker.onmessage = (e) => {
18 console.log('From worker:', e.data);
19};- In deze code gebruikt de hoofdthread
Atomics.addom de waarde atomaire te verhogen. Aan de kant vanworker.tskan toegang worden verkregen tot hetzelfdeSharedArrayBufferen kan het worden gemanipuleerd.
worker.ts (Browser Worker)
Dit is een voorbeeld waarbij de worker dezelfde gedeelde buffer ontvangt en deze periodiek verlaagt of manipuleert.
1// worker.ts
2self.onmessage = (ev: MessageEvent) => {
3 const { sab } = ev.data as { sab: SharedArrayBuffer };
4 const counter = new Int32Array(sab);
5
6 // Every 350ms, atomically add 2 (demonstration)
7 setInterval(() => {
8 const newVal = Atomics.add(counter, 0, 2) + 2;
9 // Optionally notify main thread (postMessage)
10 self.postMessage(`Worker added 2 -> ${newVal}`);
11 }, 350);
12};- De Worker manipuleert ook hetzelfde geheugen via
Int32Array, en updates gebeuren zonder race conditions dankzijAtomics.
Synchronisatie met wait/notify
Door gebruik te maken van Atomics.wait en Atomics.notify kun je threads onderbreken tot aan bepaalde voorwaarden, waardoor event-gedreven synchronisatie tussen worker threads mogelijk wordt. In browsers is het veiligste om Atomics.wait binnen een Worker te gebruiken.
producer.ts (Browser Producer Worker)
De producer schrijft data en stelt de consument op de hoogte met notify.
1// producer.ts (worker)
2self.onmessage = (ev: MessageEvent) => {
3 const { sab } = ev.data as { sab: SharedArrayBuffer };
4 const state = new Int32Array(sab); // state[0] will be 0=empty,1=filled
5
6 // produce every 500ms
7 setInterval(() => {
8 // produce: set state to 1 (filled)
9 Atomics.store(state, 0, 1);
10 Atomics.notify(state, 0, 1); // wake one waiter
11 self.postMessage('produced');
12 }, 500);
13};consumer.ts (Browser Consumer Worker)
De consument wacht met Atomics.wait en hervat verwerking na melding van de producer.
1// consumer.ts (worker)
2self.onmessage = (ev: MessageEvent) => {
3 const { sab } = ev.data as { sab: SharedArrayBuffer };
4 const state = new Int32Array(sab);
5
6 // consumer loop
7 (async function consumeLoop() {
8 while (true) {
9 // if state is 0 => wait until notified (value changes)
10 while (Atomics.load(state, 0) === 0) {
11 // Blocks this worker until notified or timeout
12 Atomics.wait(state, 0, 0);
13 }
14 // consume (reset to 0)
15 // (processing simulated)
16 // read data from another shared buffer in real scenarios
17 Atomics.store(state, 0, 0);
18 // continue loop
19 self.postMessage('consumed');
20 }
21 })();
22};- In dit patroon stelt de producer de consument op de hoogte via
Atomics.notifyen wacht de consument efficiënt metAtomics.wait.Atomics.waitkan niet worden aangeroepen op de hoofdthread in browsers. Om bevriezing van de gebruikersinterface te voorkomen, magAtomics.waitalleen in Workers worden gebruikt.
Praktisch voorbeeld met Node.js (worker_threads)
In een Node.js-omgeving kun je de functionaliteit van SharedArrayBuffer beheren met behulp van worker_threads. Hieronder staat een getypeerd TypeScript-voorbeeld voor Node.js.
main-node.ts
De hoofdthread maakt de buffer aan en geeft deze door aan de Worker.
1// main-node.ts
2import { Worker } from 'worker_threads';
3import path from 'path';
4
5// Create SharedArrayBuffer for one 32-bit integer
6const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
7const counter = new Int32Array(sab);
8
9// Spawn worker and pass sab via workerData
10const worker = new Worker(path.resolve(__dirname, 'worker-node.js'), {
11 workerData: { sab }
12});
13
14// Log messages from worker
15worker.on('message', (msg) => console.log('Worker:', msg));
16
17// Increment in main thread periodically
18setInterval(() => {
19 const val = Atomics.add(counter, 0, 1) + 1;
20 console.log('Main increment ->', val);
21}, 300);worker-node.ts
Dit voorbeeld gebruikt parentPort en workerData uit worker_threads aan de kant van de Worker.
1// worker-node.ts
2import { parentPort, workerData } from 'worker_threads';
3
4const sab = workerData.sab as SharedArrayBuffer;
5const counter = new Int32Array(sab);
6
7// Periodically add 5
8setInterval(() => {
9 const val = Atomics.add(counter, 0, 5) + 5;
10 parentPort?.postMessage(`Added 5 -> ${val}`);
11}, 700);- In Node.js zijn er geen COOP- of COEP-beperkingen zoals in browsers, dus gedeeld geheugen kan eenvoudig worden beheerd met alleen
worker_threads. Let bij het gebruik van TypeScript op of je werkt met CommonJS- of ESM-instellingen.
TypeScript-typingspunten
SharedArrayBuffer en Atomics zijn opgenomen in de standaard DOM- en library-typedefinities, dus je kunt ze direct in TypeScript gebruiken. Bij het uitwisselen van berichten met Workers is het veiliger om interfaces te definiëren en types duidelijk te specificeren.
1// Example: typed message
2type WorkerMessage = { type: 'init'; sab: SharedArrayBuffer } | { type: 'ping' };- Het expliciet definiëren van types maakt
postMessage- enonmessage-afhandeling veiliger en maakt typecontrole mogelijk.
Praktische gebruikstoepassingen
SharedArrayBuffer is niet altijd noodzakelijk, maar het is zeer effectief in situaties waar de snelheidsvoordelen van gedeeld geheugen vereist zijn. Het begrijpen van situaties waarin het effectief is, leidt tot de juiste technologische keuzes.
- Het is geschikt voor low-latency verwerking die snelle gedeelde buffers vereist, bijvoorbeeld voor real-time audio-/videoverwerking of game-physics.
- Voor eenvoudige gegevensuitwisseling of het overbrengen van grote hoeveelheden data kunnen
Transferable ArrayBufferofpostMessageeenvoudiger zijn danSharedArrayBuffer.
Beperkingen en beveiliging
Om SharedArrayBuffer in de browser te gebruiken, is cross-origin isolation vereist: stel COOP in op same-origin-allow-popups en COEP op require-corp. SharedArrayBuffer wordt uitgeschakeld als niet aan deze vereisten wordt voldaan.
Prestatie- en debugtips
Atomaire operaties (Atomics) zijn snel, maar vaak wachten en overmatige synchronisatie kunnen de latentie verhogen.
Controleer de volgende punten om gedeeld geheugen veilig en efficiënt te gebruiken.
- Views zoals
Int32Arraymoeten met de juiste byte-uitlijning worden behandeld. - Wees duidelijk welke indices van dezelfde gedeelde buffer door welke processen worden gebruikt, en houd consistente conventies in je code aan.
- Als je een
SharedArrayBufferals een gewoneArrayBufferbehandelt, treden dataraces op. Naar dezelfde index schrijven vanuit meerdere threads zonderAtomicste gebruiken is gevaarlijk.
Samenvatting
Door goed gebruik te maken van SharedArrayBuffer en Atomics kun je veilige en snelle gedeelde-geheugenoperaties uitvoeren, zelfs in TypeScript. Beginnen met eenvoudige synchronisatiepatronen zoals tellers of signalen is makkelijker te begrijpen. Door een correcte synchronisatie en indexbeheer grondig toe te passen, kun je effectief zijn, zelfs in low-latency-situaties.
Je kunt het bovenstaande artikel volgen met Visual Studio Code op ons YouTube-kanaal. Bekijk ook het YouTube-kanaal.