Concorrência e Multithreading em TypeScript: da teoria à prática
As aplicações web modernas enfrentam cada vez mais a necessidade de processar grandes volumes de dados e executar operações que consomem muitos recursos sem bloquear a interface do usuário. O TypeScript, sendo uma extensão do JavaScript, herda seu modelo de execução single-thread, mas também fornece ferramentas poderosas para organizar concorrência e multithreading. Neste artigo, vamos explorar como usar esses recursos de forma eficaz em seus projetos.
Entendendo concorrência e multithreading
Antes de nos aprofundarmos no código, é importante diferenciar dois conceitos-chave:
- Concorrência — a capacidade do sistema de lidar com várias tarefas em períodos de tempo sobrepostos, mas não necessariamente simultaneamente. Isso é alcançado através da rápida alternância de contexto.
- Multithreading — a execução paralela de várias threads em diferentes núcleos da CPU. No Node.js, isso é implementado através de Worker Threads.
O TypeScript opera no ambiente Node.js, onde a thread principal processa eventos através do Event Loop. Para cálculos pesados, isso pode se tornar um gargalo.
Assincronia e Promise: a base da concorrência
O nível básico de concorrência em TypeScript é construído sobre async/await e Promise. Esses mecanismos permitem não bloquear a thread principal durante a espera por operações de I/O.
// Exemplo de carregamento assíncrono de dadosinterface 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();}
// Execução paralela de múltiplas requisiçõesasync 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('Usuários carregados:', users.length);});Promise.all inicia todas as promises simultaneamente, mas elas são executadas em uma única thread. Isso é ideal para tarefas de I/O, mas não é adequado para operações intensivas de CPU.
Worker Threads: verdadeiro multithreading
Para executar cálculos pesados sem bloquear a thread principal, use Worker Threads. Este é um módulo embutido do Node.js que cria threads separadas com sua própria memória.
Vamos criar um exemplo de cálculo de números de Fibonacci em uma thread separada:
// 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 parou com código de saída ${code}`)); }); });}
async function main() { console.time('fibonacci'); const result = await runFibonacciInWorker(40); console.timeEnd('fibonacci'); console.log(`Fibonacci(40) = ${result}`);}
main();Observe: para executar workers em TypeScript, será necessária a compilação para JavaScript. Você pode usar ts-node ou compilação prévia.
SharedArrayBuffer: memória compartilhada entre threads
Quando as threads precisam trocar grandes volumes de da