Concurrence et multithreading en TypeScript : niveau avancé
Dans le développement web moderne et les applications serveur en TypeScript, la capacité à travailler efficacement avec la concurrence et le multithreading devient cruciale. Bien que JavaScript soit un langage monothread, l'environnement d'exécution (navigateur ou Node.js) fournit des outils puissants pour l'exécution parallèle de tâches. Dans cet article, nous explorerons les techniques avancées de gestion de la concurrence en TypeScript, notamment les Worker Threads, SharedArrayBuffer, Atomics et les modèles de synchronisation.
1. Fondamentaux : Event Loop et async/await
Avant de plonger dans le multithreading, il est important de comprendre comment fonctionne l'Event Loop en TypeScript. La concurrence dans un environnement monothread est obtenue grâce aux entrées/sorties non bloquantes et aux opérations asynchrones.
Considérons un exemple utilisant Promise.allSettled() pour l'exécution concurrente de plusieurs tâches asynchrones avec gestion des erreurs :
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 }; } });}
// Utilisationconst task1 = fetchData('/api/users');const task2 = fetchData('/api/posts');const task3 = fetchData('/api/comments');
const results = await runConcurrentTasks([task1, task2, task3]);2. Worker Threads dans Node.js : véritable multithreading
Pour les tâches intensives en CPU (par exemple, traitement d'images, cryptographie, calculs complexes), des threads réels sont nécessaires. Dans Node.js, on utilise les Worker Threads pour cela.
Créons un Worker typé avec 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);}Créons maintenant un gestionnaire pour contrôler plusieurs Workers :
// 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