Concurrencia y multihilo en TypeScript: de la teoría a la práctica
Las aplicaciones web modernas se enfrentan cada vez más a la necesidad de procesar grandes volúmenes de datos y ejecutar operaciones que consumen muchos recursos sin bloquear la interfaz de usuario. TypeScript, como superconjunto de JavaScript, hereda su modelo de ejecución de un solo hilo, pero proporciona herramientas potentes para organizar la concurrencia y el multihilo. En este artículo, analizaremos cómo utilizar eficazmente estas capacidades en sus proyectos.
Comprensión de la concurrencia y el multihilo
Antes de profundizar en el código, es importante diferenciar dos conceptos clave:
- Concurrencia — la capacidad del sistema para manejar múltiples tareas en intervalos de tiempo superpuestos, pero no necesariamente al mismo tiempo. Esto se logra mediante un cambio rápido de contexto.
- Multihilo — la ejecución paralela de múltiples hilos en diferentes núcleos de la CPU. En Node.js, esto se implementa a través de Worker Threads.
TypeScript se ejecuta en el entorno Node.js, donde el hilo principal procesa eventos a través del Event Loop. Para cálculos pesados, esto puede convertirse en un cuello de botella.
Asincronía y Promise: la base de la concurrencia
El nivel básico de concurrencia en TypeScript se basa en async/await y Promise. Estos mecanismos permiten no bloquear el hilo principal durante la espera de operaciones de I/O.
// Ejemplo de carga de datos asíncronainterface User { id: number; name: string;}
async function fetchUser(id: number): Promise<User> { const response = await fetch(`https://api.example.com/users/${id}`); return response.json();}
// Ejecución paralela de múltiples solicitudesasync function loadUsers(): Promise<User[]> { const userIds = [1, 2, 3, 4, 5]; const promises = userIds.map(id => fetchUser(id)); return Promise.all(promises);}
loadUsers().then(users => { console.log('Usuarios cargados:', users.length);});Promise.all ejecuta todas las promesas simultáneamente, pero se ejecutan en un solo hilo. Esto es ideal para tareas de I/O, pero no es adecuado para operaciones intensivas de CPU.
Worker Threads: verdadero multihilo
Para realizar cálculos pesados sin bloquear el hilo principal, utilice Worker Threads. Este es un módulo integrado de Node.js que crea hilos separados con su propia memoria.
Creemos un ejemplo de cálculo de números de Fibonacci en un hilo separado:
// fibonacci.worker.tsimport { parentPort } from 'worker_threads';
function fibonacci(n: number): number { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2);}
parentPort?.on('message', (n: number) => { const result = fibonacci(n); parentPort?.postMessage(result);});// main.tsimport { Worker } from 'worker_threads';import path from 'path';
function runFibonacciInWorker(n: number): Promise<number> { return new Promise((resolve, reject) => { const worker = new Worker(path.resolve(__dirname, 'fibonacci.worker.ts')); worker.postMessage(n); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`)); }); });}
async function main() { console.time('fibonacci'); const result = await runFibonacciInWorker(40); console.timeEnd('fibonacci'); console.log(`Fibonacci(40) = ${result}`);}
main();Tenga en cuenta: para ejecutar workers de TypeScript, se requiere compilación a JavaScript. Puede usar ts-node o compilación previa.
SharedArrayBuffer: memoria compartida entre hilos
Cuando los hilos necesitan intercambiar grandes volúmenes de da