Bootcamp backendontwikkeling met Node.js · Les

Worker threads starten en berichten doorgeven

Maak workers aan, wissel gegevens uit via postMessage en gebruik workerData en MessageChannel voor gestructureerde communicatie.

Les 2 van 413 stappen

Worker threads starten en berichten doorgeven is een gratis Bootcamp backendontwikkeling met Node.js-les op CoddyKit. Dit is les 2 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Bootcamp backendontwikkeling met Node.js. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Bootcamp backendontwikkeling met Node.js bevat in totaal 4 lessen.

Waarom workerthreads bestaan

Node.js voert uw JavaScript uit op één thread. Dat is ideaal voor I/O-gebonden werk (HTTP, databases, bestanden), omdat de gebeurtenislus nooit blokkeert. Maar een CPU-gebonden taak — afbeeldingen verkleinen, hashen, grote ladingen parseren, cryptografie — wordt synchroon uitgevoerd en bevriest de volledige server totdat de taak klaar is.

  • I/O-gebonden → blijf op de gebeurtenislus en gebruik asynchrone API's.
  • CPU-gebonden → draag het werk uit aan een Worker-thread, zodat de hoofdthread aanvragen kan blijven verwerken.

Met de module worker_threads kunt u JavaScript parallel uitvoeren op afzonderlijke threads van het besturingssysteem, elk met een eigen V8-isolate en gebeurtenislus.

Uw eerste worker starten

U maakt een thread met new Worker(filename). Het bestand wordt op een nieuwe thread uitgevoerd. Gebruik isMainThread om één bestand zich verschillend te laten gedragen afhankelijk van waar het wordt uitgevoerd.

Hieronder start de hoofdthread een worker die naar hetzelfde bestand verwijst. De tak voor de worker voert het werk uit en wordt beëindigd.

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);
}

Gegevens meegeven met workerData

Als u bij het aanmaken invoer aan een worker wilt meegeven, geeft u workerData door in het optiesobject. In de worker leest u deze uit de module worker_threads.

  • workerData wordt gekloneerd (met structured clone) en niet gedeeld — als u de gegevens in de worker wijzigt, heeft dat geen invloed op de hoofdthread.
  • Gebruik dit voor de beginparameters van de taak: een bestandspad, een getallenbereik of configuratie.
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);
}

Resultaten terugkrijgen met postMessage

workerData werkt slechts in één richting en één keer. Gebruik voor resultaten het berichtenkanaal dat elke worker ingebouwd heeft:

  • In de worker: parentPort.postMessage(value) stuurt gegevens naar het bovenliggende proces.
  • Op de hoofdthread: worker.on('message', cb) ontvangt de gegevens.

Berichten zijn asynchroon en worden gekloond met structured clone, zodat de hoofdthread responsief blijft terwijl de worker rekent.

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));
}

Berichten in twee richtingen

Het kanaal werkt in beide richtingen. Het bovenliggende proces kan worker.postMessage() gebruiken en de worker luistert met parentPort.on('message', ...). Zo wordt een worker een service met een lange levensduur die veel taken verwerkt in plaats van één.

Let erop dat de worker actief blijft luisteren naar berichten; u moet expliciet aangeven dat deze moet stoppen (hier activeert een null-sentinel 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);
  });
}

Eén taak omzetten naar een promise

In backendcode wilt u meestal een nette asynchrone functie: u roept deze aan en gebruikt await voor een resultaat. Verpak de levenscyclus van de worker in een Promise: los deze op bij message en wijs deze af bij error of een niet-nulwaarde van exit.

Dit patroon vormt de basis van elke workerpool: één promise per taak.

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);
}

Structured clone: wat u kunt versturen

Berichten worden gekopieerd met het structured-clone-algoritme, niet met JSON.stringify. Dat betekent dat u meer kunt versturen dan alleen gewone JSON.

  • Ondersteund: objecten, arrays, Map, Set, Date, RegExp, ArrayBuffer, getypeerde arrays en BigInt.
  • Niet ondersteund: functies, instanties van classes met methoden, DOM-achtige objecten en alles met closures — deze veroorzaken een DataCloneError.

Het klonen van grote objecten kost CPU en geheugen. Geef voor grote binaire buffers de voorkeur aan overdragen in plaats van kopiëren (in de volgende scène).

Buffers overdragen in plaats van kopiëren

Voor grote binaire gegevens is kopiëren verspilling. Geef een overdrachtslijst als tweede argument mee aan postMessage. Het eigendom van de ArrayBuffer wordt naar de andere thread verplaatst — zonder kopiëren — en de buffer wordt onbruikbaar op de verzendende thread (de waarde van byteLength wordt 0).

Gebruik dit voor afbeeldingsbytes, bestandsdelen of elke lading van megabyteschaal om het dupliceren van geheugen te voorkomen.

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 voor zijkanalen

Naast de ingebouwde parentPort kunt u met new MessageChannel() uw eigen kanaal maken. Dit levert twee gekoppelde poorten op: port1 en port2. Houd er één bij, draag de andere over aan een worker en u hebt een speciaal communicatiekanaal — handig om besturingsberichten van gegevens te scheiden of voor communicatie tussen workers.

Een MessagePort kan zelf worden overgedragen. U stuurt deze dus mee in workerData of in een bericht met een overdrachtslijst.

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 + ')');
  });
}

Fouten afhandelen en netjes afsluiten

Een worker kan crashen. Koppel altijd de gebeurtenissen van de levenscyclus eraan, zodat een mislukte taak niet stilletjes een aanvraag laat hangen:

  • 'error' — een niet-afgevangen uitzondering in de worker.
  • 'exit' — wordt één keer geactiveerd; code is bij een fout niet nul.
  • 'messageerror' — een bericht kon niet worden gedeserialiseerd.

Roep vanuit de hoofdthread worker.terminate() aan om een worker te stoppen (dit retourneert een promise). Workers die niet worden beëindigd, houden het proces actief.

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');
}

Een praktische backendstructuur

In een echte Node.js-backend start u niet voor elke aanvraag een worker — het opstarten van een thread is duur. In plaats daarvan:

  • Houd een kleine pool van workers met een lange levensduur bij (vaak os.cpus().length).
  • Stuur elke CPU-gebonden taak via postMessage en koppel resultaten aan een id.
  • Reserveer workers voor werk dat echt CPU-gebonden is en laat I/O op de gebeurtenislus.

Dit voorbeeld toont het berichtenprotocol per worker, waarbij een id wordt gebruikt om aanvraag en antwoord aan elkaar te koppelen — het kernidee waarop elke poolbibliotheek (zoals piscina) voortbouwt.

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 });
  });
}

Korte controle

U moet een afbeeldingsbuffer van 50 MB naar een worker sturen voor verwerking en wilt voorkomen dat dit geheugen wordt gedupliceerd. Welke aanpak is correct?

Samenvatting

U weet nu hoe u workers start en gegevens tussen threads verplaatst:

  • Starten met new Worker(file, { workerData }); vertakken op basis van isMainThread.
  • Resultaten versturen met parentPort.postMessage() en ontvangen via worker.on('message') — het kanaal werkt in twee richtingen.
  • Berichten gebruiken structured clone (rijker dan JSON, maar zonder functies); grote buffers moeten via een overdrachtslijst worden overgedragen om kopiëren te voorkomen.
  • Maak extra communicatiekanalen met MessageChannel en draag een MessagePort over voor zijkanalen of communicatie tussen workers.
  • Behandel altijd error/exit en roep terminate() aan; gebruik in productie een pool met berichten die via id's aan elkaar zijn gekoppeld, in plaats van één worker per aanvraag.
Gratis beginnen

Leer JavaScript met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
22
Lessen
92

Veelgestelde vragen

Is de les “Worker threads starten en berichten doorgeven” gratis?

Ja — de volledige tekst van “Worker threads starten en berichten doorgeven” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus Bootcamp backendontwikkeling met Node.js wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus Bootcamp backendontwikkeling met Node.js bevat in totaal 4 lessen.

Wat leer ik in “Worker threads starten en berichten doorgeven”?

Maak workers aan, wissel gegevens uit via postMessage en gebruik workerData en MessageChannel voor gestructureerde communicatie. Je oefent met Bootcamp backendontwikkeling met Node.js door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met Bootcamp backendontwikkeling met Node.js te beginnen?

Ervaring vooraf is niet nodig. Bootcamp backendontwikkeling met Node.js op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 2 van 4.

Hoe lang duurt de les “Worker threads starten en berichten doorgeven”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over Bootcamp backendontwikkeling met Node.js?

Ja. Elke les over Bootcamp backendontwikkeling met Node.js bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Waarom de event loop stilvalt bij CPU-intensief werk
  2. Worker threads starten en berichten doorgeven
  3. Geheugen delen met SharedArrayBuffer en Atomics
  4. Een herbruikbare workerpool bouwen voor throughput
← Terug naar Bootcamp backendontwikkeling met Node.js