`SharedArrayBuffer` i TypeScript

`SharedArrayBuffer` i TypeScript

Denne artikkelen forklarer SharedArrayBuffer i TypeScript.

Vi vil forklare SharedArrayBuffer i TypeScript med praktiske eksempler.

YouTube Video

SharedArrayBuffer i TypeScript

SharedArrayBuffer er en mekanisme for å dele det samme minneområdet mellom flere tråder, slik som Web Workers. Ved å kombinere det med Atomics kan du håndtere dataløp og utføre delte minneoperasjoner med lav latens.

Forutsetninger og merknader

Når du bruker SharedArrayBuffer i nettleseren, kreves COOP- og COEP-headere for å oppfylle sikkerhetskrav kjent som cross-origin isolation. I Node.js kan delt minne håndteres relativt enkelt ved å bruke worker_threads.

Grunnleggende konsepter

SharedArrayBuffer er et objekt som representerer en sekvens med fast lengde av bytes, og du kan lese og skrive tall via TypedArray og lignende visninger. Enkle lese-/skriveoperasjoner er ikke synkronisert, så du bruker Atomics-API for å sikre atomiske operasjoner og bruker wait og notify-mekanismer for koordinering.

Enkel teller (Nettleserversjon)

I dette eksemplet oppretter hovedtråden en SharedArrayBuffer og gir den videre til en Web Worker, hvor begge øker en delt teller. Dette viser det enkleste mønsteret: atomisk addisjon med Atomics.add og lesing med Atomics.load.

main.ts (Nettleserside)

Dette eksemplet viser hvordan hovedtråden oppretter en SharedArrayBuffer og deler den med en Worker for tilgang fra flere tråder.

 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};
  • I denne koden bruker hovedtråden Atomics.add for å øke verdien atomisk. På worker.ts-siden kan den samme SharedArrayBuffer nås og manipuleres.

worker.ts (Nettleser-Worker)

Dette er et eksempel der workeren mottar den samme delte bufferen og periodisk reduserer eller manipulerer den.

 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};
  • Workeren manipulerer også det samme minnet via Int32Array, og oppdateringer skjer uten dataløp takket være Atomics.

Synkronisering med wait/notify

Ved å bruke Atomics.wait og Atomics.notify kan du suspendere tråder til visse betingelser er møtt, noe som muliggjør hendelsesdrevet synkronisering mellom worker-tråder. I nettlesere er det sikreste å bruke Atomics.wait inne i en Worker.

producer.ts (Nettleser Producer Worker)

Produsenten skriver data og varsler konsumenten ved å bruke 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 (Nettleser Consumer Worker)

Konsumenten venter med Atomics.wait og gjenopptar prosesseringen etter varsling fra produsenten.

 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};
  • I dette mønsteret varsler produsenten konsumenten via Atomics.notify, og konsumenten venter effektivt med Atomics.wait. Atomics.wait kan ikke kalles på hovedtråden i nettlesere. For å forhindre at UI fryser, er Atomics.wait begrenset til bruk kun inne i Workers.

Praktisk eksempel med Node.js (worker_threads)

I et Node.js-miljø kan du håndtere SharedArrayBuffer-funksjonalitet ved å bruke worker_threads. Nedenfor er et typet TypeScript-eksempel for Node.js.

main-node.ts

Hovedtråden oppretter bufferen og gir den videre til Workeren.

 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

Dette eksemplet bruker parentPort og workerData fra worker_threads på Worker-siden.

 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);
  • I Node.js finnes det ikke COOP- eller COEP-begrensninger som i nettlesere, så delt minne kan håndteres enkelt bare ved å bruke worker_threads. Når du bruker TypeScript, vær oppmerksom på om du bygger med CommonJS- eller ESM-innstillinger.

TypeScript-typingspunkter

SharedArrayBuffer og Atomics er inkludert i de standard DOM- og biblioteks-typene, så du kan bruke dem direkte i TypeScript. Når du utveksler meldinger med Workers, er det tryggere å definere grensesnitt og spesifisere typer tydelig.

1// Example: typed message
2type WorkerMessage = { type: 'init'; sab: SharedArrayBuffer } | { type: 'ping' };
  • Eksplicit definering av typer gjør bruk av postMessage og onmessage tryggere og muliggjør typesjekking.

Praktiske bruksområder

SharedArrayBuffer er ikke alltid nødvendig, men det er svært effektivt i situasjoner hvor hastighetsfordelene med delt minne er påkrevd. Å forstå situasjonene hvor det er effektivt, fører til riktige teknologivalg.

  • Den egner seg for behandling med lav latens som krever raske delte buffere, og kan brukes til sanntids lyd-/videobehandling eller spillfysikk.
  • For enkel datautveksling eller overføring av store mengder data kan Transferable ArrayBuffer eller postMessage være enklere å bruke enn SharedArrayBuffer.

Begrensninger og sikkerhet

For å bruke SharedArrayBuffer i nettleseren kreves cross-origin isolation: sett COOP til same-origin-allow-popups og COEP til require-corp. SharedArrayBuffer vil bli deaktivert med mindre disse kravene er oppfylt.

Ytelses- og feilsøkingstips

Atomiske operasjoner (Atomics) er raske, men hyppig venting og overdreven synkronisering kan øke latenstiden.

Følgende punkter bør sjekkes for å håndtere delt minne sikkert og effektivt.

  • Visninger som Int32Array må håndteres med riktig byte-justering.
  • Vær tydelig på hvilke indekser i den samme delte bufferen som brukes av hvilke prosesser, og oppretthold konsekvente konvensjoner i koden din.
  • Hvis du behandler en SharedArrayBuffer som en vanlig ArrayBuffer, vil det oppstå dataløp. Å skrive til samme indeks fra flere tråder uten bruk av Atomics er farlig.

Sammendrag

Ved å bruke SharedArrayBuffer og Atomics riktig, kan du oppnå sikre og raske operasjoner med delt minne selv i TypeScript. Å begynne med enkle synkroniseringsmønstre som tellere eller signaler er lettere å forstå, og ved å nøye håndtere korrekt synkronisering og indekser kan du være effektiv selv i scenarier der lav latens er viktig.

Du kan følge med på artikkelen ovenfor ved å bruke Visual Studio Code på vår YouTube-kanal. Vennligst sjekk ut YouTube-kanalen.

YouTube Video