Bootcamp i backendutvikling med Node.js · leksjon

Deling av minne med SharedArrayBuffer og Atomics

Samordne tråder over delt minne med Atomics for å unngå kopiering av store buffere og hindre race conditions.

Leksjon 3 av 413 trinn

Deling av minne med SharedArrayBuffer og Atomics er en gratis leksjon i Bootcamp i backendutvikling med Node.js på CoddyKit. Dette er leksjon 3 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Bootcamp i backendutvikling med Node.js, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Bootcamp i backendutvikling med Node.js inneholder totalt 4 leksjoner.

Hvorfor delt minne?

Når De starter en Worker i Node.js og kaller postMessage, kopieres dataene De sender ved hjelp av algoritmen for strukturert kloning. For små meldinger går dette fint, men for en stor numerisk buffer (millioner av byte) sløser kopieringen med CPU-tid og minne.

  • SharedArrayBuffer lar flere tråder lese og skrive til den samme minneblokken uten kopiering.
  • Atomics gir Dem sikre operasjoner uten datakappløp på dette minnet.

Denne leksjonen viser hvordan De koordinerer tråder over delt minne i en CPU-intensiv backend-jobb.

ArrayBuffer kontra SharedArrayBuffer

En ArrayBuffer eies av én tråd. Når den overføres til en worker, mister avsenderen tilgangen til den. En SharedArrayBuffer (SAB) er annerledes: Når den sendes til en worker, deles det samme underliggende minnelageret, slik at begge trådene ser hverandres skrivninger.

De leser aldri rå byte direkte. I stedet pakker De bufferen inn i en visning, for eksempel en typet array, som Int32Array eller Float64Array.

const sab = new SharedArrayBuffer(16);
const view = new Int32Array(sab);

console.log(view.length);
view[0] = 42;
console.log(view[0]);
console.log(sab.byteLength);

Sende en SAB til en Worker

For å dele minne oppretter De SharedArrayBuffer i hovedtråden og sender den via postMessage. I motsetning til en vanlig buffer deles en SAB (den overføres ikke), slik at begge sider kan fortsette å bruke den.

Workeren pakker den samme SAB-en inn i sin egen visning av en typet array. Det skjer ingen kopiering.

const { Worker, isMainThread, workerData } = require('worker_threads');

if (isMainThread) {
  const sab = new SharedArrayBuffer(4);
  const view = new Int32Array(sab);
  view[0] = 100;
  new Worker(__filename, { workerData: sab });
} else {
  const view = new Int32Array(workerData);
  view[0] += 1;
  console.log('worker sees', view[0]);
}

Problemet med datakappløp

Vanlige lesinger og skrivninger i en delt visning er ikke sikre når flere tråder bruker samme plass. En instruksjon som view[0] += 1 består egentlig av tre trinn: les, legg til og skriv. To tråder kan flette seg inn i hverandre, slik at oppdateringer går tapt.

  • Tråd A leser 5, og tråd B leser 5.
  • Begge beregner 6 og skriver 6.
  • Det skjedde to økninger, men verdien økte bare med én.

Dette er et klassisk datakappløp. Løsningen er Atomics.

Atomics.add for sikre tellere

Atomics.add(view, index, value) utfører les–endre–skriv som én udelelig operasjon. Ingen annen tråd kan flette seg inn, så økninger går aldri tapt.

Andre nyttige metoder er Atomics.sub, Atomics.and, Atomics.or og Atomics.load / Atomics.store for vanlige lesinger og skrivninger som garantert er synlige på tvers av tråder.

const sab = new SharedArrayBuffer(4);
const counter = new Int32Array(sab);

Atomics.store(counter, 0, 0);
Atomics.add(counter, 0, 5);
Atomics.add(counter, 0, 3);

console.log(Atomics.load(counter, 0));

Fordele CPU-arbeid på flere tråder

Se for Dem at De summerer en enorm array med heltall, en CPU-intensiv oppgave som ville blokkert hendelsesløkken. Med delt minne lagrer De dataene én gang og lar flere workere behandle hver sin del, mens de skriver delresultater til en delt resultatplass via Atomics.add.

Fordi inndataene ligger i en SharedArrayBuffer, kopierer De aldri datasettet til hver worker. Alle leser de samme bytene.

Sammenligning og utveksling

Atomics.compareExchange(view, index, expected, replacement) skriver replacement bare hvis den gjeldende verdien er lik expected, og returnerer verdien som var der. Dette er byggesteinen for låsfrie algoritmer og enkle spinlocks.

Bruk den til å reservere en plass nøyaktig én gang: Hvis byttet lykkes, vant denne tråden reserveringen.

const sab = new SharedArrayBuffer(4);
const slot = new Int32Array(sab);
Atomics.store(slot, 0, 0);

const prev = Atomics.compareExchange(slot, 0, 0, 1);
console.log('previous value was', prev);
console.log('claimed:', prev === 0);

const again = Atomics.compareExchange(slot, 0, 0, 1);
console.log('second claim succeeded:', again === 0);

Blokkering med Atomics.wait

Noen ganger må en worker settes på pause til en annen tråd signaliserer den. Atomics.wait(view, index, expectedValue) blokkerer tråden som kaller den, så lenge plassen fortsatt inneholder expectedValue. Den returnerer 'ok', 'not-equal' eller 'timed-out'.

  • Atomics.wait fungerer bare utenfor hovedtråden (ellers ville hendelsesløkken blitt fryst).
  • Atomics.notify(view, index, count) vekker tråder som venter.

Dette gir Dem en ekte trådbarriere uten aktiv løkkekjøring.

Vekke workere med Notify

Produsenttråden oppdaterer den delte plassen med Atomics.store og kaller deretter Atomics.notify for å vekke tråder som venter i Atomics.wait. Rekkefølgen er viktig: Endre verdien først, og varsle deretter.

I en ekte backend-jobb er dette måten en koordinator slipper alle workerne løs samtidig for å starte en fase, eller signaliserer at inndataene er klare.

const { Worker, isMainThread, workerData } = require('worker_threads');

if (isMainThread) {
  const sab = new SharedArrayBuffer(4);
  const signal = new Int32Array(sab);
  Atomics.store(signal, 0, 0);
  new Worker(__filename, { workerData: sab });
  setTimeout(() => {
    Atomics.store(signal, 0, 1);
    Atomics.notify(signal, 0, 1);
  }, 50);
} else {
  const signal = new Int32Array(workerData);
  Atomics.wait(signal, 0, 0);
  console.log('worker released, value =', Atomics.load(signal, 0));
}

En komplett parallell summering

Her er hele mønsteret i én kjørbar fil: en delt inndatabuffer, en delt resultatplass og to workere som summerer hver sin halvdel av dataene og atomisk legger delresultatet sitt til resultatet. Hovedtråden venter på at begge skal bli ferdige.

Legg merke til at inndataene aldri kopieres; begge workerne leser den samme SharedArrayBuffer.

const { Worker, isMainThread, workerData } = require('worker_threads');

if (isMainThread) {
  const N = 1000;
  const dataSab = new SharedArrayBuffer(N * 4);
  const data = new Int32Array(dataSab);
  for (let i = 0; i < N; i++) data[i] = i + 1;

  const resultSab = new SharedArrayBuffer(8);
  const result = new Int32Array(resultSab);
  Atomics.store(result, 0, 0);
  Atomics.store(result, 1, 0);

  let done = 0;
  const ranges = [[0, N / 2], [N / 2, N]];
  for (const [start, end] of ranges) {
    const w = new Worker(__filename, { workerData: { dataSab, resultSab, start, end } });
    w.on('exit', () => {
      if (++done === ranges.length) {
        console.log('total =', Atomics.load(result, 0));
      }
    });
  }
} else {
  const { dataSab, resultSab, start, end } = workerData;
  const data = new Int32Array(dataSab);
  const result = new Int32Array(resultSab);
  let local = 0;
  for (let i = start; i < end; i++) local += data[i];
  Atomics.add(result, 0, local);
}

Praktiske forholdsregler

Delt minne er kraftig, men lett å bruke feil. Ha disse reglene i mente:

  • SharedArrayBuffer lagrer bare tall. For å dele strenger eller objekter må De kode dem (for eksempel med TextEncoder til en Uint8Array).
  • Bruk alltid Atomics for alle plasser som mer enn én tråd kan skrive til; en blanding av vanlige og atomiske skrivninger introduserer datakappløp igjen.
  • Reserver en fast plass for synkroniseringsflagg, og dokumenter indeksen.
  • Bruk bare delt minne når kopiering faktisk er en flaskehals; for de fleste meldinger er vanlig postMessage enklere og trygt.

Hurtigsjekk

De har flere worker-tråder som øker én delt teller lagret i en Int32Array som støttes av en SharedArrayBuffer. Hvilken fremgangsmåte holder tellingen korrekt ved samtidig kjøring?

Oppsummering

De har lært hvordan De koordinerer Node.js-workertråder over delt minne:

  • SharedArrayBuffer deler ett underliggende minnelager mellom tråder uten kopiering; pakk den inn i en typet array, for eksempel Int32Array.
  • Vanlig += på en delt plass fører til datakappløp; bruk Atomics for alle plasser som skrives til samtidig.
  • Atomics.add, Atomics.load, Atomics.store og Atomics.compareExchange gir kappløpsfrie lesinger, skrivninger og låsfrie reserveringer.
  • Atomics.wait (utenfor hovedtråden) sammen med Atomics.notify lar tråder blokkere og signalisere uten aktiv løkkekjøring.
  • Bruk delt minne først når kopiering av store numeriske buffere faktisk er en flaskehals.
Gratis å komme i gang

Lær deg JavaScript med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
22
Leksjoner
92

Ofte stilte spørsmål

Er leksjonen «Deling av minne med SharedArrayBuffer og Atomics» gratis?

Ja – hele teksten i «Deling av minne med SharedArrayBuffer og Atomics» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av Bootcamp i backendutvikling med Node.js-kurset, kan du oppgradere til CoddyKit PRO. Kurset i Bootcamp i backendutvikling med Node.js inneholder totalt 4 leksjoner.

Hva lærer jeg i «Deling av minne med SharedArrayBuffer og Atomics»?

Samordne tråder over delt minne med Atomics for å unngå kopiering av store buffere og hindre race conditions. Du øver på Bootcamp i backendutvikling med Node.js med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Bootcamp i backendutvikling med Node.js?

Ingen tidligere erfaring er nødvendig. Bootcamp i backendutvikling med Node.js på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 3 av 4.

Hvor lang tid tar leksjonen «Deling av minne med SharedArrayBuffer og Atomics»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Bootcamp i backendutvikling med Node.js-leksjonen?

Ja. Alle Bootcamp i backendutvikling med Node.js-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Hvorfor event loop stopper ved CPU-bundet arbeid
  2. Oppretting av worker-tråder og utveksling av meldinger
  3. Deling av minne med SharedArrayBuffer og Atomics
  4. Bygging av en gjenbrukbar worker-pool for gjennomstrømming
← Tilbake til Bootcamp i backendutvikling med Node.js