Parallelität und Multithreading in JavaScript: Von A bis Z
JavaScript ist historisch gesehen eine Single-Thread-Sprache, die im Rahmen des ereignisgesteuerten Programmierparadigmas arbeitet. Das moderne JavaScript (insbesondere in der Node.js-Umgebung) bietet jedoch leistungsstarke Werkzeuge für die Arbeit mit Parallelität und Multithreading. In diesem Artikel werden wir uns ansehen, wie Event Loop, Promises, async/await, Worker Threads und Clusterbildung funktionieren und wie Sie diese Mechanismen nutzen können, um die Leistung Ihrer Anwendungen zu steigern.
1. Event Loop: Die Grundlage der Parallelität in JavaScript
Der Event Loop ist das Herzstück des asynchronen JavaScript-Modells. Er ermöglicht die Verarbeitung vieler Operationen, ohne den Hauptausführungs-Thread zu blockieren. In Node.js verwaltet der Event Loop Aufgabenwarteschlangen (Macrotasks und Microtasks) und gewährleistet so eine nicht blockierende Ein-/Ausgabe.
Beispiel für die Funktionsweise des Event Loops:
console.log('Start');
setTimeout(() => { console.log('Timer 1');}, 0);
process.nextTick(() => { console.log('nextTick');});
Promise.resolve().then(() => { console.log('Promise');});
console.log('Ende');
// Ausgabe:// Start// Ende// nextTick// Promise// Timer 1Es ist wichtig, die Ausführungsreihenfolge zu verstehen: Synchroner Code → Microtasks (process.nextTick, Promise.then) → Macrotasks (setTimeout, setInterval).
2. Promises und async/await: Asynchronität verwalten
Promises sind Objekte, die das Ergebnis einer asynchronen Operation darstellen. Sie helfen, die „Callback-Hölle“ zu vermeiden und ermöglichen die Erstellung von Aufrufketten. async/await ist syntaktischer Zucker für Promises, der asynchronen Code wie synchronen Code aussehen lässt.
Beispiel mit async/await:
const fetchData = async (url) => { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP-Fehler: ${response.status}`); } return await response.json();};
const main = async () => { try { const data = await fetchData('https://api.example.com/data'); console.log('Empfangene Daten:', data); } catch (error) { console.error('Fehler:', error.message); }};
main();Dieser Ansatz ermöglicht die parallele Ausführung mehrerer asynchroner Operationen mit Promise.all():
const [user, posts] = await Promise.all([ fetchUser(userId), fetchPosts(userId)]);console.log(user, posts);3. Worker Threads: Echtes Multithreading in Node.js
Seit Node.js 10.5.0 bietet das Modul worker_threads die Möglichkeit, echte Threads für CPU-intensive Aufgaben zu erstellen. Jeder Worker läuft in einem separaten Thread mit einer eigenen V8-Instanz.
Beispiel für die Erstellung eines Workers:
// main.jsconst { Worker } = require('worker_threads');
const 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 wurde mit Code ${code} beendet`)); } }); });};
const main = async () => { const result = await runWorker({ iterations: 1000000 }); console.log('Ergebnis:', result);};
main();// worker.jsconst { parentPort, workerData } = require('worker_threads');
const heavyComputation = (iterations) => { let result = 0; for (let i = 0; i < iterations; i++) { result += Math.sqrt(i); } return result;};
const result = heavyComputation(workerData.iterations);parentPort.postMessage(result);Worker Threads eignen sich ideal für Aufgaben, die den Event Loop blockieren: Bildverarbeitung, mathematische Berechnungen, Parsen großer Dateien.