Parallelität und Multithreading in TypeScript: Fortgeschrittenes Niveau
In der modernen Webentwicklung und bei serverseitigen Anwendungen in TypeScript wird die Fähigkeit, effektiv mit Parallelität und Multithreading zu arbeiten, zunehmend kritisch. Obwohl JavaScript eine single-threaded Sprache ist, bietet die Laufzeitumgebung (Browser oder Node.js) leistungsstarke Werkzeuge für die parallele Ausführung von Aufgaben. In diesem Artikel betrachten wir fortgeschrittene Techniken für die Arbeit mit Parallelität in TypeScript, einschließlich Worker Threads, SharedArrayBuffer, Atomics und Synchronisationsmustern.
1. Grundlagen: Event Loop und async/await
Bevor wir uns in Multithreading vertiefen, ist es wichtig zu verstehen, wie der Event Loop in TypeScript funktioniert. Parallelität in einer single-threaded Umgebung wird durch nicht-blockierende Ein-/Ausgabe und asynchrone Operationen erreicht.
Betrachten wir ein Beispiel mit Promise.allSettled() für die parallele Ausführung mehrerer asynchroner Aufgaben mit Fehlerbehandlung:
interface TaskResult<T> { status: 'fulfilled' | 'rejected'; value?: T; reason?: Error;}
async function runConcurrentTasks<T>( tasks: Promise<T>[]): Promise<TaskResult<T>[]> { const results = await Promise.allSettled(tasks); return results.map(result => { if (result.status === 'fulfilled') { return { status: 'fulfilled', value: result.value }; } else { return { status: 'rejected', reason: result.reason }; } });}
// Verwendungconst task1 = fetchData('/api/users');const task2 = fetchData('/api/posts');const task3 = fetchData('/api/comments');
const results = await runConcurrentTasks([task1, task2, task3]);2. Worker Threads in Node.js: Echte Multithreading
Für CPU-intensive Aufgaben (z. B. Bildverarbeitung, Kryptografie, komplexe Berechnungen) sind echte Threads erforderlich. In Node.js werden dafür Worker Threads verwendet.
Erstellen wir einen typisierten Worker mit TypeScript:
// worker.tsimport { parentPort, workerData } from 'worker_threads';
interface WorkerInput { data: number[]; operation: 'sum' | 'multiply';}
interface WorkerOutput { result: number; duration: number;}
if (parentPort) { const input: WorkerInput = workerData; const start = performance.now(); let result: number; if (input.operation === 'sum') { result = input.data.reduce((acc, val) => acc + val, 0); } else { result = input.data.reduce((acc, val) => acc * val, 1); } const output: WorkerOutput = { result, duration: performance.now() - start }; parentPort.postMessage(output);}Jetzt erstellen wir einen Manager zur Steuerung mehrerer Worker:
// threadPool.tsimport { Worker } from 'worker_threads';import path from 'path';
class ThreadPool<TInput, TOutput> { private workers: Worker[] = []; private taskQueue: Array<{ input: TInput; resolve: (value: TOutput) => void; reject: (reason?: any) => void; }> = [];
constructor( private workerScript: string, private poolSize: number = 4 ) { this.initialize(); }
private initialize(): void { for (let i = 0; i < this.poolSize; i++) { const worker = new Worker(this.workerScript); worker.on('message', (result: TOutput) => { const task = this.taskQueue.shift(); if (task) { task.resolve(result); } worker.postMessage(this.taskQueue[0]?.input); });
worker.on('error', (error) => { const task = this.taskQueue.shift(); if (task) { task.reject(error); } });
this.workers.push(worker); } }
async execute(input: TInput): Promise<TOutput> { return new Promise((resolve, reject) => { this.ta