Bootcamp i backendudvikling med Node.js · Lektion

Oprettelse af worker threads og udveksling af beskeder

Opret workers, udveksl data via postMessage, og brug workerData og MessageChannel til struktureret kommunikation.

Lektion 2 af 413 trin

Oprettelse af worker threads og udveksling af beskeder er en gratis Bootcamp i backendudvikling med Node.js-lektion på CoddyKit. Dette er lektion 2 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 worker-tråde findes

Node.js kører din JavaScript på en enkelt tråd. Det er fremragende til I/O-bundet arbejde (HTTP, DB, filer), fordi event loop aldrig blokerer. Men en CPU-bundet opgave — ændring af billedstørrelse, hashing, parsing af enorme nyttelaster eller kryptografi — kører synkront og fastfryser hele serveren, indtil den er færdig.

  • I/O-bundet → bliv på event loop, og brug asynkrone API'er.
  • CPU-bundet → overlad arbejdet til en Worker-tråd, så hovedtråden fortsat kan håndtere forespørgsler.

Modulet worker_threads lader dig køre JavaScript parallelt på separate OS-tråde, hver med sit eget V8-isolat og sin egen event loop.

Opret din første worker

Du opretter en tråd med new Worker(filename). Filen kører på en ny tråd. Brug isMainThread til at få én fil til at opføre sig forskelligt afhængigt af, hvor den kører.

Nedenfor opretter hovedtråden en worker, der peger tilbage på den samme fil. Worker-grenen udfører arbejdet og afslutter.

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

if (isMainThread) {
  console.log('main: spawning worker');
  const worker = new Worker(__filename);
  worker.on('exit', (code) => console.log('worker exited with', code));
} else {
  // This branch runs inside the worker thread
  let sum = 0;
  for (let i = 0; i < 1e7; i++) sum += i;
  console.log('worker computed sum =', sum);
}

Send data ind med workerData

Hvis du vil give en worker input, når den oprettes, skal du sende workerData med i indstillingsobjektet. Inde i workeren læser du det fra modulet worker_threads.

  • workerData bliver klonet (struktureret kloning), ikke delt — ændringer i workeren påvirker ikke hovedtråden.
  • Brug det til de oprindelige parametre for opgaven: en filsti, et talinterval eller en konfiguration.
const { Worker, isMainThread, workerData } = require('node:worker_threads');

if (isMainThread) {
  new Worker(__filename, { workerData: { start: 1, end: 5 } });
} else {
  const { start, end } = workerData;
  let product = 1;
  for (let i = start; i <= end; i++) product *= i;
  console.log('factorial-ish product =', product);
}

Hent resultater tilbage med postMessage

workerData går kun én vej og kan kun bruges én gang. Til resultater skal du bruge den meddelelseskanal, som alle workere har indbygget:

  • Inde i workeren: parentPort.postMessage(value) sender data til forælderen.
  • På hovedtråden: worker.on('message', cb) modtager dataene.

Meddelelser er asynkrone og klones struktureret, så hovedtråden forbliver responsiv, mens workeren beregner.

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

if (isMainThread) {
  const worker = new Worker(__filename, { workerData: 40 });
  worker.on('message', (result) => {
    console.log('main received:', result);
  });
} else {
  function fib(n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); }
  parentPort.postMessage(fib(workerData));
}

Tovejskommunikation

Kanalen fungerer i begge retninger. Forælderen kan kalde worker.postMessage(), og workeren lytter med parentPort.on('message', ...). Det gør en worker til en langlivet tjeneste, der behandler mange opgaver i stedet for én.

Bemærk, at workeren forbliver aktiv og lytter efter meddelelser; du skal udtrykkeligt bede den om at stoppe (her udløser et null-sentinelkald process.exit).

const { Worker, isMainThread, parentPort } = require('node:worker_threads');

if (isMainThread) {
  const worker = new Worker(__filename);
  worker.on('message', (r) => console.log('squared ->', r));
  [2, 3, 4].forEach((n) => worker.postMessage(n));
  worker.postMessage(null); // sentinel to stop
} else {
  parentPort.on('message', (n) => {
    if (n === null) { process.exit(0); }
    parentPort.postMessage(n * n);
  });
}

Gør en enkelt opgave til en promise

I backend-kode vil du normalt have en ren asynkron funktion: kald den, og brug await på et resultat. Pak workerens livscyklus ind i en Promise, der løses ved message og afvises ved error eller en exit med en kode forskellig fra nul.

Dette mønster er grundlaget for enhver worker-pool: én promise pr. opgave.

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

function runJob(payload) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(__filename, { workerData: payload });
    worker.on('message', resolve);
    worker.on('error', reject);
    worker.on('exit', (code) => {
      if (code !== 0) reject(new Error('exit code ' + code));
    });
  });
}

if (isMainThread) {
  runJob([5, 10, 15]).then((sum) => console.log('total =', sum));
} else {
  const total = workerData.reduce((a, b) => a + b, 0);
  parentPort.postMessage(total);
}

Struktureret kloning: Hvad du kan sende

Meddelelser kopieres ved hjælp af algoritmen til struktureret kloning, ikke JSON.stringify. Det betyder, at du kan sende mere end almindelig JSON.

  • Understøttet: objekter, arrays, Map, Set, Date, RegExp, ArrayBuffer, typede arrays og BigInt.
  • Ikke understøttet: funktioner, klasseinstanser med metoder, DOM-lignende objekter og alt med closures — disse udløser en DataCloneError.

Kloning af store objekter koster CPU og hukommelse. Ved store binære buffere bør du foretrække overførsel frem for kopiering (næste scene).

Overfør buffere i stedet for at kopiere

Ved store binære data er kopiering spild af ressourcer. Send en overførselsliste som det andet argument til postMessage. Ejerskabet af ArrayBuffer flyttes til den anden tråd — uden kopiering — og bufferen bliver ubrugelig på afsenderen (dens byteLength bliver 0).

Brug dette til billedbytes, filbidder eller enhver nyttelast på megabyte-størrelse for at undgå at duplikere hukommelsen.

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

if (isMainThread) {
  const buf = new Uint8Array([1, 2, 3, 4]).buffer;
  const worker = new Worker(__filename, { workerData: buf }, { transferList: [buf] });
  worker.on('message', (sum) => console.log('byte sum =', sum));
} else {
  const bytes = new Uint8Array(workerData);
  let sum = 0;
  for (const b of bytes) sum += b;
  parentPort.postMessage(sum);
}

MessageChannel til sidekanaler

Ud over den indbyggede parentPort kan du oprette din egen kanal med new MessageChannel(). Den giver dig to forbundne porte, port1 og port2. Behold den ene, overfør den anden til en worker, og så har du en dedikeret kanal — nyttig til at adskille kontrolmeddelelser fra data eller til kommunikation mellem workere.

En MessagePort kan selv overføres, så du sender den i workerData eller en meddelelse med en overførselsliste.

const { Worker, isMainThread, MessageChannel, parentPort, workerData } =
  require('node:worker_threads');

if (isMainThread) {
  const { port1, port2 } = new MessageChannel();
  new Worker(__filename, { workerData: { port: port2 }, transferList: [port2] });
  port1.on('message', (msg) => {
    console.log('main got on side channel:', msg);
    port1.close();
  });
  port1.postMessage('ping');
} else {
  const { port } = workerData;
  port.on('message', (msg) => {
    port.postMessage('pong (you said: ' + msg + ')');
  });
}

Håndtering af fejl og korrekt nedlukning

En worker kan gå ned. Tilslut altid dens livscyklushændelser, så en mislykket opgave ikke får en forespørgsel til at hænge ubemærket:

  • 'error' — en uindfanget undtagelse inde i workeren.
  • 'exit' — udløses én gang; code er forskellig fra nul ved fejl.
  • 'messageerror' — en meddelelse kunne ikke deserialiseres.

Hvis du vil stoppe en worker fra hovedtråden, skal du kalde worker.terminate() (returnerer en promise). Workere, der ikke er afsluttet, holder processen i live.

const { Worker, isMainThread, parentPort } = require('node:worker_threads');

if (isMainThread) {
  const worker = new Worker(__filename);
  worker.on('error', (err) => console.log('caught worker error:', err.message));
  worker.on('exit', (code) => console.log('exited, code =', code));
} else {
  throw new Error('boom inside worker');
}

En praktisk backend-struktur

I en rigtig Node.js-backend opretter du ikke en worker pr. forespørgsel — opstart af tråde er dyrt. I stedet:

  • Behold en lille pool af langlivede workere (ofte os.cpus().length).
  • Send hver CPU-bundne opgave via postMessage, og match resultaterne ved hjælp af et id.
  • Reserver workere til reelt CPU-bundet arbejde, og lad I/O blive på event loop.

Dette kodestykke viser meddelelsesprotokollen pr. worker, hvor et id bruges til at forbinde forespørgsel og svar — den centrale idé, som alle pool-biblioteker (som piscina) bygger på.

const { Worker, isMainThread, parentPort } = require('node:worker_threads');

if (isMainThread) {
  const worker = new Worker(__filename);
  const pending = new Map();
  let nextId = 0;
  worker.on('message', ({ id, result }) => {
    pending.get(id)(result);
    pending.delete(id);
    if (pending.size === 0) worker.terminate();
  });
  function submit(payload) {
    return new Promise((res) => {
      const id = nextId++;
      pending.set(id, res);
      worker.postMessage({ id, payload });
    });
  }
  Promise.all([submit(3), submit(7)]).then((r) => console.log('results:', r));
} else {
  parentPort.on('message', ({ id, payload }) => {
    parentPort.postMessage({ id, result: payload * payload });
  });
}

Hurtigt tjek

Du skal sende en billedbuffer på 50 MB til en worker til behandling, og du vil undgå at duplikere hukommelsen. Hvilken fremgangsmåde er korrekt?

Opsummering

Du ved nu, hvordan du opretter workere og flytter data mellem tråde:

  • Opret med new Worker(file, { workerData }), og vælg gren ud fra isMainThread.
  • Send resultater med parentPort.postMessage(), og modtag dem via worker.on('message') — kanalen fungerer i begge retninger.
  • Meddelelser bruger struktureret kloning (mere omfattende end JSON, men uden funktioner); store buffere bør overføres via en overførselsliste for at undgå kopiering.
  • Opret ekstra kanaler med MessageChannel, og overfør en MessagePort til sidekanaler eller kanaler mellem workere.
  • Håndter altid error/exit, og kald terminate(); i produktion skal du bruge en pool med meddelelser, der knyttes sammen via id, i stedet for én worker pr. forespørgsel.
Gratis at komme i gang

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 “Oprettelse af worker threads og udveksling af beskeder” gratis?

Ja — hele teksten til “Oprettelse af worker threads og udveksling af beskeder” 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 “Oprettelse af worker threads og udveksling af beskeder”?

Opret workers, udveksl data via postMessage, og brug workerData og MessageChannel til struktureret kommunikation. 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 2 af 4.

Hvor lang tid tager lektionen “Oprettelse af worker threads og udveksling af beskeder”?

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

  1. Hvorfor event loop går i stå ved CPU-bundet arbejde
  2. Oprettelse af worker threads og udveksling af beskeder
  3. Deling af hukommelse med SharedArrayBuffer og Atomics
  4. Opbygning af en genanvendelig worker-pool til gennemløb
← Tilbage til Bootcamp i backendudvikling med Node.js