Parallelität und Multithreading in JavaScript: Fortgeschrittenes Niveau
JavaScript war historisch gesehen eine Single-Thread-Sprache, die im Rahmen der Ereignisschleife (Event Loop) arbeitet. Moderne Anwendungen auf Node.js erfordern jedoch eine hohe Leistung bei der Verarbeitung CPU-intensiver Aufgaben. In diesem Artikel werden wir fortgeschrittene Mechanismen der Parallelität und des Multithreadings untersuchen: Worker Threads, SharedArrayBuffer, Atomics und Clustering. Sie erfahren, wie Sie Mehrkernprozessoren effektiv nutzen und Blockierungen des Hauptthreads vermeiden.
1. Worker Threads: Echtes Multithreading in Node.js
Das Modul worker_threads wurde in Node.js 10.5.0 eingeführt und ermöglicht die Erstellung separater Threads mit eigener V8-Engine. Im Gegensatz zum Clustering (Prozesse) teilen sich Worker Threads den Speicher und können Daten über SharedArrayBuffer austauschen.
Erstellen und Starten eines Workers
// main.jsconst { Worker } = require('worker_threads');
function runWorker(data) { return new Promise((resolve, reject) => { const worker = new Worker('./worker.js', { workerData: data }); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) reject(new Error(`Worker mit Exit-Code ${code} gestoppt`)); }); });}
(async () => { const result = await runWorker({ numbers: [1, 2, 3, 4, 5] }); console.log('Ergebnis:', result); // { sum: 15 }})();// worker.jsconst { parentPort, workerData } = require('worker_threads');
const sum = workerData.numbers.reduce((acc, val) => acc + val, 0);parentPort.postMessage({ sum });Wichtig: Worker haben keinen Zugriff auf das DOM oder Module, die den Hauptthread erfordern. Verwenden Sie sie für schwere Berechnungen, Bildverarbeitung oder Verschlüsselung.
2. SharedArrayBuffer und Atomics: Gemeinsam genutzter Speicher
SharedArrayBuffer ermöglicht mehreren Workern das Lesen und Schreiben von Daten in denselben Speicherbereich. Zur Synchronisation des Zugriffs wird das Objekt Atomics verwendet – es garantiert atomare Operationen.
Beispiel: Parallele Verarbeitung eines Arrays
// main.jsconst { Worker } = require('worker_threads');const { SharedArrayBuffer } = require('worker_threads');
const buffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 10);const array = new Int32Array(buffer);
// Array füllenfor (let i = 0; i < 10; i++) array[i] = i;
const worker1 = new Worker('./worker_atomics.js', { workerData: { start: 0, end: 5 } });const worker2 = new Worker('./worker_atomics.js', { workerData: { start: 5, end: 10 } });
worker1.on('message', () => console.log('Worker 1 abgeschlossen'));worker2.on('message', () => console.log('Worker 2 abgeschlossen'));// worker_atomics.jsconst { parentPort, workerData } = require('worker_threads');const { Atomics } = require('worker_threads');
const buffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 10);const array = new Int32Array(buffer);
// Atomare Erhöhung jedes Elementsfor (let i = workerData.start; i < workerData.end; i++) { Atomics.add(array, i, 10);}
parentPort.postMessage('done');Hinweis: Verwenden Sie Atomics.wait() und Atomics.notify() zur Synchronisation von Threads (Analogon zu Mutexen). Dies ist entscheidend, um Wettlaufsituationen (Race Conditions) zu verhindern.
3. Clustering: Skalierung durch Prozesse
Das Modul cluster erstellt mehrere Node.js-Prozesse (Worker), von denen jeder Anfragen verarbeitet. Dies ist die Standardmethode zur Skalierung von HTTP-Servern auf Mehrkernsystemen.
Beispiel: Geclusterter HTTP-Server
const cluster = require('cluster');const http = require('http');const numCPUs = require('os').cpus().length;
if (cluster.isMaster) { console.