Deling af hukommelse med SharedArrayBuffer og Atomics
Koordinér tråde over delt hukommelse med Atomics for at undgå kopiering af store buffere og forhindre race conditions.
Deling af hukommelse med SharedArrayBuffer og Atomics er en gratis Bootcamp i backendudvikling med Node.js-lektion på CoddyKit. Dette er lektion 3 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Bootcamp i backendudvikling med Node.js, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Bootcamp i backendudvikling med Node.js-kurset indeholder 4 lektioner i alt.
Hvorfor delt hukommelse?
Når du starter en Worker i Node.js og kalder postMessage, bliver de data, du sender, kopieret ved hjælp af algoritmen til struktureret kloning. For små meddelelser er det fint, men for en stor numerisk buffer (millioner af bytes) spilder kopieringen CPU-tid og hukommelse.
- SharedArrayBuffer gør det muligt for flere tråde at læse og skrive i den samme hukommelsesblok uden kopiering.
- Atomics giver dig sikre operationer uden race conditions på den hukommelse.
Denne lektion viser, hvordan du koordinerer tråde over delt hukommelse i et CPU-bundet backend-job.
ArrayBuffer sammenlignet med SharedArrayBuffer
En ArrayBuffer ejes af én tråd. Når den overføres til en worker, mister afsenderen adgangen til den. En SharedArrayBuffer (SAB) fungerer anderledes: Når den sendes til en worker, deles det samme underliggende lager, så begge tråde kan se hinandens skrivninger.
Du læser aldrig rå bytes direkte. I stedet pakker du bufferen ind i en visning som en typet array, for eksempel 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 at dele hukommelse skal du oprette SharedArrayBuffer i hovedtråden og sende den via postMessage. I modsætning til en normal buffer deles en SAB (den overføres ikke), så begge sider kan fortsætte med at bruge den.
Workeren pakker den samme SAB ind i sin egen visning af en typet array. Der sker 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 race conditions
Almindelige læsninger og skrivninger på en delt visning er ikke sikre, når flere tråde tilgår den samme plads. En sætning som view[0] += 1 består i virkeligheden af tre trin: læs, læg til, skriv. To tråde kan flette deres trin sammen og dermed miste opdateringer.
- Tråd A læser 5, og tråd B læser 5.
- Begge beregner 6 og skriver 6.
- Der skete to inkrementeringer, men værdien steg kun med én.
Dette er en klassisk datarace. Løsningen er Atomics.
Atomics.add til sikre tællere
Atomics.add(view, index, value) udfører læs-ændr-skriv som én udelelig operation. Ingen anden tråd kan flette sig ind, så inkrementeringer går aldrig tabt.
Andre nyttige metoder er Atomics.sub, Atomics.and, Atomics.or og Atomics.load / Atomics.store til almindelige læsninger og skrivninger, som garanteret er synlige på tværs af tråde.
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-arbejde på flere tråde
Forestil dig, at du summerer et enormt array af heltal – en CPU-bundet opgave, der ellers ville blokere event loopet. Med delt hukommelse gemmer du dataene én gang og lader flere workere behandle hver sin del og skrive delresultater til en delt resultatplads via Atomics.add.
Da inputtet ligger i en SharedArrayBuffer, kopierer du aldrig datasættet til hver worker. De læser alle de samme bytes.
Sammenlign og udskift
Atomics.compareExchange(view, index, expected, replacement) skriver replacement kun hvis den aktuelle værdi er lig med expected, og returnerer den værdi, der stod der. Dette er grundlaget for låsefri algoritmer og simple spinlocks.
Brug den til at gøre krav på en plads præcis én gang: Hvis udskiftningen lykkes, vandt denne tråd retten til pladsen.
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);Blokering med Atomics.wait
Nogle gange skal en worker vente, indtil en anden tråd sender et signal. Atomics.wait(view, index, expectedValue) blokerer den kaldende tråd, så længe pladsen stadig indeholder expectedValue. Den returnerer 'ok', 'not-equal' eller 'timed-out'.
Atomics.waitfungerer kun uden for hovedtråden (ellers ville den fryse event loopet).Atomics.notify(view, index, count)vækker ventende tråde.
Det giver dig en ægte trådbarriere uden aktiv venten i en løkke.
Notify vækker workere
Producenttråden opdaterer den delte plads med Atomics.store og kalder derefter Atomics.notify for at vække tråde, der venter i Atomics.wait. Rækkefølgen er vigtig: Skift først værdien, og send derefter besked.
I et rigtigt backend-job er det sådan, en koordinator frigiver alle workere på én gang for at starte en fase eller signalerer, at inputtet er klar.
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 komplet parallel sum
Her er hele mønsteret i én kørbar fil: en delt inputbuffer, en delt resultatplads og to workere, der hver summerer halvdelen af dataene og atomisk lægger deres delresultat til resultatet. Hovedtråden venter på, at begge bliver færdige.
Bemærk, at inputtet aldrig kopieres; begge workere læser fra 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 forbehold
Delt hukommelse er effektiv, men let at bruge forkert. Husk disse regler:
SharedArrayBuffergemmer kun tal. Hvis du vil dele strenge eller objekter, skal du kode dem (for eksempel medTextEncodertil enUint8Array).- Brug altid
Atomicstil enhver plads, som mere end én tråd kan skrive til; en blanding af almindelige og atomiske skrivninger genindfører race conditions. - Reserver en fast plads til synkroniseringsflag, og dokumenter dens indeks.
- Brug kun delt hukommelse, når kopiering reelt er en flaskehals; for de fleste meddelelser er almindelig
postMessageenklere og sikker.
Hurtigt tjek
Du har flere worker-tråde, der inkrementerer én delt tæller, som er gemt i et Int32Array baseret på en SharedArrayBuffer. Hvilken tilgang holder tællingen korrekt ved samtidighed?
Opsummering
Du har lært, hvordan du koordinerer Node.js-workertråde over delt hukommelse:
- SharedArrayBuffer deler ét underliggende lager mellem tråde uden kopiering; pak den ind i en typet array som
Int32Array. - Almindelig
+=på en delt plads medfører dataraces; brug Atomics til enhver plads, som skrives til samtidigt. Atomics.add,Atomics.load,Atomics.storeogAtomics.compareExchangegiver race-frie læsninger, skrivninger og låsefri krav på pladser.Atomics.wait(uden for hovedtråden) sammen medAtomics.notifylader tråde blokere og sende signaler uden aktiv venten i en løkke.- Brug kun delt hukommelse, når kopiering af store numeriske buffere reelt er en flaskehals.
Lær JavaScript med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 22
- Lektioner
- 92
Ofte stillede spørgsmål
Er lektionen “Deling af hukommelse med SharedArrayBuffer og Atomics” gratis?
Ja — hele teksten til “Deling af hukommelse med SharedArrayBuffer og Atomics” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Bootcamp i backendudvikling med Node.js-kurset, skal du opgradere til CoddyKit PRO. Bootcamp i backendudvikling med Node.js-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Deling af hukommelse med SharedArrayBuffer og Atomics”?
Koordinér tråde over delt hukommelse med Atomics for at undgå kopiering af store buffere og forhindre race conditions. Du øver dig i Bootcamp i backendudvikling med Node.js med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på Bootcamp i backendudvikling med Node.js?
Der kræves ingen tidligere erfaring. Bootcamp i backendudvikling med Node.js på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 3 af 4.
Hvor lang tid tager lektionen “Deling af hukommelse med SharedArrayBuffer og Atomics”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne Bootcamp i backendudvikling med Node.js-lektion?
Ja. Alle Bootcamp i backendudvikling med Node.js-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Hvorfor event loop går i stå ved CPU-bundet arbejde
- Oprettelse af worker threads og udveksling af beskeder
- Deling af hukommelse med SharedArrayBuffer og Atomics
- Opbygning af en genanvendelig worker-pool til gennemløb