Concurrencia y subprocesamiento múltiple en TypeScript: nivel avanzado
En el desarrollo web moderno y las aplicaciones de servidor en TypeScript, la capacidad de trabajar eficientemente con concurrencia y subprocesamiento múltiple se vuelve críticamente importante. Aunque JavaScript es un lenguaje de un solo hilo, el entorno de ejecución (navegador o Node.js) proporciona herramientas potentes para la ejecución paralela de tareas. En este artículo, exploraremos técnicas avanzadas para trabajar con concurrencia en TypeScript, incluyendo Worker Threads, SharedArrayBuffer, Atomics y patrones de sincronización.
1. Fundamentos básicos: Event Loop y async/await
Antes de sumergirnos en el subprocesamiento múltiple, es importante entender cómo funciona el Event Loop en TypeScript. La concurrencia en un entorno de un solo hilo se logra mediante E/S no bloqueante y operaciones asíncronas.
Consideremos un ejemplo usando Promise.allSettled() para la ejecución concurrente de múltiples tareas asíncronas con manejo de errores:
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 }; } });}
// Usoconst task1 = fetchData('/api/users');const task2 = fetchData('/api/posts');const task3 = fetchData('/api/comments');
const results = await runConcurrentTasks([task1, task2, task3]);2. Worker Threads en Node.js: verdadero subprocesamiento múltiple
Para tareas intensivas en CPU (por ejemplo, procesamiento de imágenes, criptografía, cálculos complejos) se necesitan hilos reales. En Node.js, se utilizan Worker Threads para esto.
Creemos un Worker tipado con 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);}Ahora creemos un administrador para gestionar múltiples 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