Dela minne med SharedArrayBuffer och Atomics
Samordna trådar över delat minne med Atomics för att undvika kopiering av stora buffertar och förhindra race conditions.
Dela minne med SharedArrayBuffer och Atomics är en gratis lektion i Bootcamp i backendutveckling med Node.js på CoddyKit. Detta är lektion 3 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för Bootcamp i backendutveckling med Node.js, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Bootcamp i backendutveckling med Node.js innehåller totalt 4 lektioner.
Varför delat minne?
När du startar en Worker i Node.js och anropar postMessage kopieras datan du skickar med structured clone-algoritmen. För små meddelanden är det inget problem, men för en stor numerisk buffer (miljontals byte) slösar kopieringen med CPU och minne.
- SharedArrayBuffer låter flera trådar läsa och skriva i samma minnesblock utan kopiering.
- Atomics ger säkra operationer på minnet utan race conditions.
I den här lektionen visas hur trådar samordnas via delat minne i ett CPU-intensivt backend-jobb.
ArrayBuffer jämfört med SharedArrayBuffer
En ArrayBuffer ägs av en tråd. När den överförs till en worker förlorar avsändaren åtkomsten till den. En SharedArrayBuffer (SAB) fungerar annorlunda: när den skickas till en worker delas samma underliggande minneslagring, så båda trådarna ser varandras skrivningar.
Du läser aldrig råa byte direkt. I stället omsluter du buffern med en vy i form av en typad array, till exempel 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);Skicka en SAB till en Worker
Om du vill dela minne skapar du SharedArrayBuffer i huvudtråden och skickar den via postMessage. Till skillnad från en vanlig buffer delas en SAB (den överförs inte), så båda sidor kan fortsätta använda den.
Workern omsluter samma SAB i sin egen vy i form av en typad array. Ingen kopiering sker.
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
Vanliga läsningar och skrivningar i en delad vy är inte säkra när flera trådar använder samma plats. En sats som view[0] += 1 består egentligen av tre steg: läs, addera, skriv. Två trådar kan växla mellan stegen och göra att uppdateringar går förlorade.
- Tråd A läser 5 och tråd B läser 5.
- Båda beräknar 6 och skriver 6.
- Två inkrementeringar utfördes, men värdet ökade bara med ett.
Detta är en klassisk data race. Lösningen är Atomics.
Atomics.add för säkra räknare
Atomics.add(view, index, value) utför läsning, ändring och skrivning som en enda odelbar operation. Ingen annan tråd kan komma emellan, så inga inkrementeringar går förlorade.
Andra användbara metoder är Atomics.sub, Atomics.and, Atomics.or samt Atomics.load / Atomics.store för vanliga läsningar och skrivningar som garanterat syns mellan trådar.
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));Dela upp CPU-arbete mellan trådar
Föreställ dig att du summerar en enorm array med heltal, en CPU-intensiv uppgift som skulle blockera händelseslingan. Med delat minne lagrar du datan en gång och låter flera workers bearbeta varsin del och skriva delresultat till en delad resultatplats via Atomics.add.
Eftersom indata finns i en SharedArrayBuffer behöver du aldrig kopiera datamängden till varje worker. Alla läser samma byte.
Jämför och byt
Atomics.compareExchange(view, index, expected, replacement) skriver replacement endast om det aktuella värdet är lika med expected, och returnerar värdet som fanns där. Detta är en grund byggsten för lockfria algoritmer och enkla spinlocks.
Använd den för att göra anspråk på en plats exakt en gång: om bytet lyckas vann den här tråden anspråket.
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);Blockering med Atomics.wait
Ibland måste en worker pausa tills en annan tråd signalerar till den. Atomics.wait(view, index, expectedValue) blockerar den anropande tråden så länge platsen fortfarande innehåller expectedValue. Den returnerar 'ok', 'not-equal' eller 'timed-out'.
Atomics.waitfungerar endast utanför huvudtråden (annars skulle händelseslingan frysas).Atomics.notify(view, index, count)väcker väntande trådar.
Detta ger en riktig trådbarriär utan aktiv väntan i en loop.
Notifiera för att väcka workers
Producenttråden uppdaterar den delade platsen med Atomics.store och anropar sedan Atomics.notify för att väcka trådar som väntar i Atomics.wait. Ordningen är viktig: ändra värdet först och notifiera sedan.
I ett riktigt backend-jobb är det så här en koordinator släpper iväg alla workers samtidigt för att starta en fas eller signalerar att indata är 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 komplett parallell summering
Här är hela mönstret i en körbar fil: en delad indata-buffer, en delad resultatplats och två workers som summerar varsin halva av datan och atomiskt adderar sitt delresultat till resultatet. Huvudtråden väntar på att båda ska bli klara.
Observera att indata aldrig kopieras; båda workers läser samma 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);
}Praktiska försiktighetsåtgärder
Delat minne är kraftfullt men lätt att använda fel. Tänk på följande regler:
SharedArrayBufferlagrar endast tal. Om du vill dela strängar eller objekt måste du koda dem (till exempel medTextEncodertill enUint8Array).- Använd alltid
Atomicsför alla platser som fler än en tråd kan skriva till; om du blandar vanliga skrivningar med atomiska skrivningar uppstår race conditions igen. - Reservera en fast plats för synkroniseringsflaggor och dokumentera dess index.
- Använd endast delat minne när kopiering verkligen är en flaskhals; för de flesta meddelanden är vanligt
postMessageenklare och säkert.
Snabb kontroll
Du har flera worker-trådar som inkrementerar en delad räknare i en Int32Array som backas av en SharedArrayBuffer. Vilken metod håller räkningen korrekt vid samtidig åtkomst?
Sammanfattning
Du har lärt dig hur Node.js-workertrådar samordnas via delat minne:
- SharedArrayBuffer delar samma underliggande minneslagring mellan trådar utan kopiering; omslut den i en typad array som
Int32Array. - Vanlig
+=på en delad plats orsakar data races; använd Atomics för alla platser som skrivs till samtidigt. Atomics.add,Atomics.load,Atomics.storeochAtomics.compareExchangeger race-fria läsningar, skrivningar och lockfria anspråk.Atomics.wait(utanför huvudtråden) tillsammans medAtomics.notifylåter trådar blockera och signalera utan aktiv väntan i en loop.- Använd delat minne endast när kopiering av stora numeriska buffrar verkligen är en flaskhals.
Lär dig JavaScript med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 22
- Lektioner
- 92
Vanliga frågor
Är lektionen ”Dela minne med SharedArrayBuffer och Atomics” gratis?
Ja – hela texten till ”Dela minne med SharedArrayBuffer och Atomics” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i Bootcamp i backendutveckling med Node.js, kan Ni uppgradera till CoddyKit PRO. Kursen i Bootcamp i backendutveckling med Node.js innehåller totalt 4 lektioner.
Vad lär jag mig i ”Dela minne med SharedArrayBuffer och Atomics”?
Samordna trådar över delat minne med Atomics för att undvika kopiering av stora buffertar och förhindra race conditions. Ni övar på Bootcamp i backendutveckling med Node.js med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig Bootcamp i backendutveckling med Node.js?
Du behöver inga förkunskaper. Utbildningen i Bootcamp i backendutveckling med Node.js på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.
Hur lång tid tar lektionen ”Dela minne med SharedArrayBuffer och Atomics”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här Bootcamp i backendutveckling med Node.js-lektionen?
Ja. Varje Bootcamp i backendutveckling med Node.js-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Varför händelseslingan stannar vid CPU-bundet arbete
- Skapa worker-trådar och skicka meddelanden
- Dela minne med SharedArrayBuffer och Atomics
- Bygg en återanvändbar worker-pool för genomströmning