`SharedArrayBuffer` i TypeScript

`SharedArrayBuffer` i TypeScript

Den här artikeln förklarar SharedArrayBuffer i TypeScript.

Vi kommer att förklara SharedArrayBuffer i TypeScript med praktiska exempel.

YouTube Video

SharedArrayBuffer i TypeScript

SharedArrayBuffer är en mekanism för att dela samma minnesutrymme mellan flera trådar, såsom Web Workers. Genom att kombinera det med Atomics kan du hantera datakonflikter och utföra shared memory-operationer med låg latens.

Förutsättningar och anmärkningar

När du använder SharedArrayBuffer i webbläsaren krävs COOP- och COEP-headrar för att uppfylla säkerhetskraven som kallas cross-origin isolation. I Node.js kan delat minne hanteras relativt enkelt med hjälp av worker_threads.

Grundläggande begrepp

SharedArrayBuffer är ett objekt som representerar en sekvens av bytes med fast längd, och du kan läsa och skriva siffror via TypedArray och liknande vyer. Enkla läs- och skrivoperationer är inte synkroniserade, så du använder Atomics-API:et för att säkerställa atomära operationer och använder wait- och notify-mekanismer för samordning.

Enkel räknare (webbläsarversion)

I det här exemplet skapar huvudtråden en SharedArrayBuffer och skickar den till en Web Worker, där båda ökar en delad räknare. Detta visar det minsta mönstret: atomär addition med Atomics.add och läsning med Atomics.load.

main.ts (webbläsarsidan)

Detta exempel visar hur huvudtråden skapar en SharedArrayBuffer och delar den med en Worker för tillgång från flera trådar.

 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 denna kod använder huvudtråden Atomics.add för att öka värdet atomärt. På worker.ts-sidan kan samma SharedArrayBuffer nås och manipuleras.

worker.ts (Webbläsar-Worker)

Detta är ett exempel där workern tar emot samma delade buffert och med jämna mellanrum minskar eller manipulerar 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};
  • Workern manipulerar också samma minne via Int32Array, och uppdateringar görs utan datakonflikter tack vare Atomics.

Synkronisering med wait/notify

Genom att använda Atomics.wait och Atomics.notify kan du pausa trådar tills vissa villkor uppfylls, vilket möjliggör händelsedriven synkronisering inom worker-trådar. I webbläsare är det säkrast att använda Atomics.wait inuti en Worker.

producer.ts (Webbläsar-Producer-Worker)

Producenten skriver data och meddelar konsumenten med hjälp av 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 (Webbläsar-Consumer-Worker)

Konsumenten väntar med Atomics.wait och återupptar bearbetningen vid meddelande från 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 detta mönster meddelar producenten konsumenten via Atomics.notify och konsumenten väntar effektivt med hjälp av Atomics.wait. Atomics.wait kan inte anropas på huvudtråden i webbläsare. För att förhindra att användargränssnittet fryser är Atomics.wait begränsad till att endast användas inom Workers.

Praktiskt exempel med Node.js (worker_threads)

I en Node.js-miljö kan du hantera SharedArrayBuffer-funktionalitet med hjälp av worker_threads. Nedan finns ett TypeScript-exempel med typer för Node.js.

main-node.ts

Huvudtråden skapar bufferten och skickar den till Workern.

 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

Detta exempel använder parentPort och workerData från worker_threads på Worker-sidan.

 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 finns det inga COOP- eller COEP-restriktioner som i webbläsare, så delat minne kan enkelt hanteras bara genom att använda worker_threads. När du använder TypeScript, var uppmärksam på om du bygger med CommonJS- eller ESM-inställningar.

TypeScript-typningspunkter

SharedArrayBuffer och Atomics ingår i de standardiserade DOM- och bibliotekstypdefinitionerna, så du kan använda dem direkt i TypeScript. När du utbyter meddelanden med Workers är det säkrare att definiera gränssnitt och ange typer tydligt.

1// Example: typed message
2type WorkerMessage = { type: 'init'; sab: SharedArrayBuffer } | { type: 'ping' };
  • Genom att uttryckligen definiera typer blir hanteringen av postMessage och onmessage säkrare och möjliggör typkontroll.

Praktiska användningsområden

SharedArrayBuffer är inte alltid nödvändig, men den är mycket effektiv i situationer där hastighetsfördelarna med delat minne behövs. Att förstå i vilka situationer det är effektivt leder till lämpliga teknikval.

  • Det är lämpligt för låg-latensbearbetning som kräver snabba delade buffertar och kan användas för realtids ljud-/videobearbetning eller spel-fysik.
  • För enkel dataöverföring eller överföring av stora mängder data kan Transferable ArrayBuffer eller postMessage vara enklare att använda än SharedArrayBuffer.

Begränsningar och säkerhet

För att använda SharedArrayBuffer i webbläsaren krävs cross-origin isolation: sätt COOP till same-origin-allow-popups och COEP till require-corp. SharedArrayBuffer kommer att inaktiveras om dessa krav inte uppfylls.

Prestanda- och felsökningstips

Atomära operationer (Atomics) är snabba, men frekvent väntan och överdriven synkronisering kan öka latensen.

Följande punkter bör kontrolleras för att hantera delat minne säkert och effektivt.

  • Vyer som Int32Array bör hanteras med korrekt byteuppradning.
  • Var tydlig med vilka index i samma delade buffert som används av vilka processer, och håll konsekventa konventioner i din kod.
  • Om du behandlar en SharedArrayBuffer som en vanlig ArrayBuffer kommer datakonflikter att uppstå. Att skriva till samma index från flera trådar utan att använda Atomics är farligt.

Sammanfattning

Genom att använda SharedArrayBuffer och Atomics på rätt sätt kan du uppnå säkra och snabba shared memory-operationer även i TypeScript. Att börja med enkla synkroniseringsmönster som räknare eller signaler är lättare att förstå, och genom att noggrant hantera korrekt synkronisering och index kan du vara effektiv även i scenarier med låg latens.

Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.

YouTube Video