Concorrência e Multithreading em JavaScript: Nível Avançado
JavaScript historicamente foi uma linguagem single-thread, operando dentro do loop de eventos (Event Loop). No entanto, aplicações modernas em Node.js exigem alta performance ao processar tarefas intensivas de CPU. Neste artigo, vamos explorar mecanismos avançados de concorrência e multithreading: Worker Threads, SharedArrayBuffer, Atomics e clusterização. Você aprenderá como utilizar efetivamente processadores multi-core e evitar o bloqueio da thread principal.
1. Worker Threads: verdadeiro multithreading em Node.js
O módulo worker_threads foi introduzido no Node.js 10.5.0 e permite criar threads separadas com seu próprio motor V8. Diferente da clusterização (processos), as Worker Threads compartilham memória e podem trocar dados através de SharedArrayBuffer.
Criação e execução de um worker
// main.js
const { Worker } = require('worker_threads');
function runWorker(data) {
return new Promise((resolve, reject) => {
const worker = new Worker('./worker.js', { workerData: data });
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 () => {
const result = await runWorker({ numbers: [1, 2, 3, 4, 5] });
console.log('Resultado:', result); // { sum: 15 }
})();
// worker.js
const { parentPort, workerData } = require('worker_threads');
const sum = workerData.numbers.reduce((acc, val) => acc + val, 0);
parentPort.postMessage({ sum });
Importante: Workers não têm acesso ao DOM ou a módulos que exigem a thread principal. Utilize-os para cálculos pesados, processamento de imagens ou criptografia.
2. SharedArrayBuffer e Atomics: memória compartilhada
SharedArrayBuffer permite que múltiplos workers leiam e escrevam dados em uma mesma área de memória. Para sincronizar o acesso, utiliza-se o objeto Atomics — ele garante operações atômicas.
Exemplo: processamento paralelo de array
// main.js
const { Worker } = require('worker_threads');
const { SharedArrayBuffer } = require('worker_threads');
const buffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 10);
const array = new Int32Array(buffer);
// Preenche o array
for (let i = 0; i < 10; i++) array[i] = i;
const worker1 = new Worker('./worker_atomics.js', { workerData: { start: 0, end: 5 } });
const worker2 = new Worker('./worker_atomics.js', { workerData: { start: 5, end: 10 } });
worker1.on('message', () => console.log('Worker 1 finalizou'));
worker2.on('message', () => console.log('Worker 2 finalizou'));
// worker_atomics.js
const { parentPort, workerData } = require('worker_threads');
const { Atomics } = require('worker_threads');
const buffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 10);
const array = new Int32Array(buffer);
// Incremento atômico de cada elemento
for (let i = workerData.start; i < workerData.end; i++) {
Atomics.add(array, i, 10);
}
parentPort.postMessage('done');
Nota: Use Atomics.wait() e Atomics.notify() para sincronização de threads (análogo a mutexes). Isso é crítico para prevenir condições de corrida.
3. Clusterização: escalando através de processos
O módulo cluster cria múltiplos processos Node.js (workers), cada um processando requisições. Esta é a forma padrão para escalar servidores HTTP em sistemas multi-core.
Exemplo: servidor HTTP clusterizado
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.```