Parallelität und Multithreading in JavaScript: Event Loop und Worker Threads

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einführung: Mythen und Realität der Mehrfädrigkeit in JavaScript

JavaScript wird traditionell mit einem single-threaded Ausführungsmodell assoziiert. Viele Entwickler, besonders diejenigen, die aus der Welt von C++ oder Java kommen, betrachten dies als Einschränkung. Das moderne Node.js-Ökosystem bietet jedoch leistungsstarke Werkzeuge für die Arbeit mit Nebenläufigkeit und, was noch wichtiger ist, mit echter Mehrfädrigkeit. In diesem Artikel werden wir untersuchen, wie die Event Loop funktioniert, wann Worker Threads verwendet werden sollten und wie man hochlastige Anwendungen richtig aufbaut.

Im Gegensatz zur klassischen Mehrfädrigkeit, bei der jeder Thread seinen eigenen Stack hat und parallel auf verschiedenen CPU-Kernen ausgeführt werden kann, verwendet JavaScript im Browser und Node.js ein ereignisbasiertes Nebenläufigkeitsmodell. Mit dem Aufkommen des Moduls worker_threads in Node.js haben wir jedoch die Möglichkeit echten Parallelismus erhalten.



Hauptteil



1. Event Loop: Das Herz der Nebenläufigkeit in Node.js

Die Event Loop ist ein Mechanismus, der es Node.js ermöglicht, nicht-blockierende Ein-/Ausgabeoperationen (I/O) durchzuführen, trotz der Single-Threaded-Natur von JavaScript. Sie überprüft ständig die Aufgabenwarteschlange und führt Aufgaben aus, sobald sie eintreffen.

Die Hauptphasen der Event Loop:

  • Timers — Ausführung von Callbacks, die über setTimeout() und setInterval() geplant wurden.
  • I/O callbacks — Verarbeitung abgeschlossener Ein-/Ausgabeoperationen.
  • Idle, prepare — Interne Phasen von Node.js.
  • Poll — Abrufen neuer I/O-Ereignisse und Ausführung ihrer Callbacks.
  • Check — Ausführung von setImmediate()-Callbacks.
  • Close callbacks — Verarbeitung von Schließereignissen (z.B. socket.on('close', ...)).

Beispiel für die Funktionsweise der Event Loop:

const fs = require('fs');

console.log('Programmstart');

setTimeout(() => { console.log('Timer ausgelöst');}, 0);

setImmediate(() => { console.log('setImmediate ausgeführt');});

fs.readFile(__filename, () => { console.log('Datei gelesen');});

console.log('Programmende');

// Ausgabe:// Programmstart// Programmende// setImmediate ausgeführt// Timer ausgelöst// Datei gelesen

Es ist wichtig zu verstehen: Die Event Loop macht den Code nicht multithreaded, sie verteilt lediglich effizient die Zeit zwischen Aufgaben. Wenn eine Aufgabe die CPU zu lange beansprucht (z.B. eine komplexe Schleife), wird die gesamte Event Loop blockiert.

2. Das Blockierungsproblem und die Lösung: Worker Threads

Für CPU-intensive Aufgaben (Verschlüsselung, Bildverarbeitung, Parsing großer Datenmengen) ist die Event Loop ungeeignet – sie würde den gesamten Server blockieren. Hier kommen Worker Threads zur Hilfe.

Worker Threads sind ein Node.js-Modul, mit dem echte Betriebssystem-Threads erstellt werden können. Jeder Worker hat seine eigene V8-Instanz und Event Loop, was die parallele Ausführung von Code ermöglicht.

Ein grundlegendes Beispiel für die Verwendung von Worker Threads:

// main.jsconst { Worker } = require('worker_threads');

function runWorker(workerData) { return new Promise((resolve, reject) => { const worker = new Worker('./worker.js', { workerData }); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) { reject(new Error(`Worker mit Code ${code} gestoppt`)); } }); });}

async function main() { console.log('Worker wird gestartet...'); const result = await runWorker(42); console.log('Ergebnis:', result);}

main().catch(console.error);

// worker.jsconst { parentPort, workerData } = require('worker_threads');

// CPU-intensive Aufgabefunction fibonacci(n) { if (n < 2) return n; return fibonacci(n - 1) + fibonacci(n -

Blogs

Buchempfehlungen