Parallelität und Multithreading in JavaScript: Wie man Code schneller macht
JavaScript war historisch gesehen eine Single-Thread-Sprache, aber das bedeutet nicht, dass es nicht mehrere Aufgaben gleichzeitig verarbeiten kann. In modernem Node.js und Browsern gibt es leistungsstarke Mechanismen für die Arbeit mit Parallelität und Multithreading. Das Verständnis dieser Konzepte ist entscheidend für die Erstellung leistungsstarker Server- und Client-Anwendungen.
In diesem Artikel werden wir untersuchen, wie sich Parallelität von Nebenläufigkeit unterscheidet, wie der Event Loop funktioniert, wann Worker Threads und Web Workers verwendet werden sollten, und praktische Beispiele mit Node.js betrachten.
1. Event Loop und nicht-blockierende Ein-/Ausgabe
Die Grundlage der Parallelität in JavaScript ist der Event Loop (Ereignisschleife). Dies ist ein Mechanismus, der es ermöglicht, asynchrone Operationen (z. B. Dateilesen, Datenbankabfragen) ohne Blockierung des Hauptthreads auszuführen.
Stellen Sie sich eine Warteschlange vor: Der Event Loop nimmt eine Aufgabe aus der Warteschlange (call stack), führt sie aus, und wenn er auf eine asynchrone Operation trifft (z. B. setTimeout oder fetch), übergibt er sie an die Web API (im Browser) oder libuv (in Node.js). Nach Abschluss der Operation gelangt ihr Callback in die Aufgabenwarteschlange (task queue). Der Event Loop überprüft ständig, ob der Stack leer ist, und wenn dies der Fall ist, nimmt er die nächste Aufgabe aus der Warteschlange.
Beispiel für asynchronen Code:
const fs = require('fs');
console.log('Start');
fs.readFile('example.txt', 'utf8', (err, data) => { if (err) throw err; console.log('Datei gelesen:', data);});
console.log('Ende');
// Ausgabe:// Start// Ende// Datei gelesen: ...Beachten Sie: console.log('Ende') wird vor dem Lesen der Datei ausgeführt. Dies ist die nicht-blockierende Ein-/Ausgabe – der Hauptthread wartet nicht auf den Abschluss der Operation, sondern führt die Ausführung fort.
2. Web Workers und Worker Threads: Echtes Multithreading
Obwohl der Event Loop hervorragend mit I/O-Operationen umgeht, hilft er nicht bei der Ausführung "schwerer" Berechnungen (Bildverarbeitung, Kryptographie, komplexe Sortierungen). Wenn solcher Code im Hauptthread ausgeführt wird, "hängt" die gesamte Oberfläche (oder der Server) bis zum Abschluss der Berechnungen.
Zur Lösung dieses Problems gibt es Web Workers (im Browser) und Worker Threads (in Node.js). Sie ermöglichen die Ausführung von JavaScript-Code in separaten Betriebssystem-Threads.
Beispiel mit Worker Threads in Node.js
Erstellen wir eine Datei worker.js:
const { parentPort } = require('worker_threads');
// Schwere Aufgabe: Berechnung der Summe von Zahlenlet sum = 0;for (let i = 0; i < 1e9; i++) { sum += i;}
// Senden des Ergebnisses zurück an den HauptthreadparentPort.postMessage(sum);Hauptdatei main.js:
const { Worker } = require('worker_threads');
console.log('Hauptthread: Worker wird gestartet');
const worker = new Worker('./worker.js');
worker.on('message', (result) => { console.log('Ergebnis vom Worker:', result);});
worker.on('error', (err) => { console.error('Worker-Fehler:', err);});
worker.on('exit', (code) => { if (code !== 0) { console.error(`Worker mit Code ${code} beendet`); }});
console.log('Hauptthread: Wir arbeiten weiter...');
// Ausgabe:// Hauptthread: Worker wird gestartet// Hauptthread: Wir arbeiten weiter...// Ergebnis vom Worker: 499999999500000000Wie Sie sehen, wird der Hauptthread nicht blockiert, während der Worker die Berechnungen im Hintergrund durchführt. Dies ist echtes Multithreading.
3. Async/Await und Promise: Verwaltung von Parallelität
Modernes JavaScript bietet elegante Möglichkeiten für die Arbeit mit asynchronem Code – async/await und Promise. Sie erstellen keine neuen Threads, a