`SharedArrayBuffer` i TypeScript

`SharedArrayBuffer` i TypeScript

Denne artikel forklarer SharedArrayBuffer i TypeScript.

Vi vil forklare SharedArrayBuffer i TypeScript med praktiske eksempler.

YouTube Video

SharedArrayBuffer i TypeScript

SharedArrayBuffer er en mekanisme til at dele den samme hukommelse mellem flere tråde, såsom Web Workers. Ved at kombinere det med Atomics kan du håndtere datakapløb og udføre hurtige delte hukommelsesoperationer med lav latenstid.

Forudsætninger og Bemærkninger

Når du bruger SharedArrayBuffer i browseren, kræves COOP- og COEP-headere for at opfylde sikkerhedskravene kendt som cross-origin isolation. I Node.js kan delt hukommelse håndteres forholdsvis nemt ved hjælp af worker_threads.

Grundlæggende Koncepter

SharedArrayBuffer er et objekt, der repræsenterer en sekvens af bytes med fast længde, og du kan læse og skrive tal via TypedArray og lignende views. Enkle læse/skriv-operationer er ikke synkroniseret, så du bruger Atomics API'et til at sikre atomare operationer og bruger wait og notify mekanismer til koordinering.

Simpel Tæller (Browserversion)

I dette eksempel opretter hovedtråden en SharedArrayBuffer og giver den videre til en Web Worker, hvor begge øger en delt tæller. Dette demonstrerer det minimale mønster: atomar addition med Atomics.add og læsning med Atomics.load.

main.ts (Browser-side)

Dette eksempel viser, hvordan hovedtråden opretter en SharedArrayBuffer og deler den med en Worker for adgang med flere tråde.

 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 kode bruger hovedtråden Atomics.add til at øge værdien atomart. På worker.ts-siden kan den samme SharedArrayBuffer tilgås og manipuleres.

worker.ts (Browser Worker)

Dette er et eksempel, hvor worker'en modtager den samme delte buffer og periodisk formindsker 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};
  • Worker'en manipulerer også den samme hukommelse via Int32Array, og opdateringer foretages uden datakapløb takket være Atomics.

Synkronisering med wait/notify

Ved at bruge Atomics.wait og Atomics.notify kan du suspendere tråde indtil visse betingelser er opfyldt, hvilket muliggør hændelsesbaseret synkronisering i worker-tråde. I browsere er det sikreste at bruge Atomics.wait inde i en Worker.

producer.ts (Browser Producer Worker)

Producenten skriver data og underretter forbrugeren ved hjælp af 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)

Forbrugeren venter med Atomics.wait og genoptager behandlingen ved underretning fra producenten.

 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ønster underretter producenten forbrugeren via Atomics.notify, og forbrugeren venter effektivt med Atomics.wait. Atomics.wait kan ikke kaldes på hovedtråden i browsere. For at undgå at brugerfladen fryser, er brugen af Atomics.wait begrænset til kun at blive brugt i Workers.

Praktisk Eksempel med Node.js (worker_threads)

I et Node.js-miljø kan du håndtere SharedArrayBuffer-funktionalitet ved hjælp af worker_threads. Nedenfor er et typed TypeScript-eksempel til Node.js.

main-node.ts

Hovedtråden opretter bufferen og viderebringer den til 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

Dette eksempel bruger 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 er der ingen COOP- eller COEP-begrænsninger som i browsere, så delt hukommelse kan nemt håndteres blot ved at anvende worker_threads. Når du bruger TypeScript, skal du være opmærksom på, om du bygger med CommonJS- eller ESM-indstillinger.

TypeScript Typings Tips

SharedArrayBuffer og Atomics er inkluderet i de standard DOM- og biblioteks-typede definitioner, så du kan bruge dem direkte i TypeScript. Når du udveksler beskeder med Workers, er det sikrest at definere interfaces og tydeligt angive typer.

1// Example: typed message
2type WorkerMessage = { type: 'init'; sab: SharedArrayBuffer } | { type: 'ping' };
  • Eksplicit definition af typer gør håndtering af postMessage og onmessage mere sikker og muliggør typekontrol.

Praktiske Anvendelser

SharedArrayBuffer er ikke altid nødvendig, men den er meget effektiv i situationer, hvor der kræves hastighedsfordele ved delt hukommelse. Forståelse af de situationer, hvor det er effektivt, fører til passende teknologivalg.

  • Det er velegnet til behandlinger med lav latenstid, der kræver højhastigheds delte buffere, og kan benyttes til realtids lyd-/videobehandling eller fysik i spil.
  • Til simpel dataudveksling eller overførsel af store datamængder kan Transferable ArrayBuffer eller postMessage være lettere at bruge end SharedArrayBuffer.

Begrænsninger og Sikkerhed

For at bruge SharedArrayBuffer i browseren kræves cross-origin isolation: sæt COOP til same-origin-allow-popups og COEP til require-corp. SharedArrayBuffer vil blive deaktiveret, medmindre disse krav er opfyldt.

Ydeevne og Fejlfindingsråd

Atomare operationer (Atomics) er hurtige, men hyppig venten og overdreven synkronisering kan øge latenstiden.

Følgende punkter bør kontrolleres for at håndtere delt hukommelse sikkert og effektivt.

  • Views som Int32Array bør håndteres med korrekt bytejustering.
  • Vær tydelig om hvilke indekser af den samme delte buffer, der bruges af hvilke processer, og oprethold konsistente konventioner i din kode.
  • Hvis du behandler en SharedArrayBuffer som en almindelig ArrayBuffer, vil der opstå datakapløb. At skrive til samme indeks fra flere tråde uden brug af Atomics er farligt.

Sammendrag

Ved korrekt brug af SharedArrayBuffer og Atomics kan du opnå sikre og hurtige delte hukommelsesoperationer, selv i TypeScript. Det er lettere at forstå at begynde med simple synkroniseringsmønstre som tællere eller signaler, og ved omhyggelig håndtering af korrekt synkronisering og indekser kan du være effektiv, selv i situationer med lave latenstider.

Du kan følge med i ovenstående artikel ved hjælp af Visual Studio Code på vores YouTube-kanal. Husk også at tjekke YouTube-kanalen.

YouTube Video