Concorrência e Multithreading em JavaScript: Nível Avançado
\n\nJavaScript é tradicionalmente considerado uma linguagem single-thread, mas o ecossistema moderno do Node.js oferece ferramentas poderosas para trabalhar com concorrência e verdadeiro multithreading. Neste artigo, abordaremos técnicas avançadas: desde os detalhes do Event Loop até o uso de Worker Threads, SharedArrayBuffer e Atomics. Você aprenderá a distribuir carga de forma eficiente, evitar condições de corrida e construir aplicações escaláveis.
\n\n1. Event Loop, Microtasks e Macrotasks: O Fundamento da Concorrência
\n\nEntender o Event Loop é a chave para gerenciar a concorrência sem bloqueios. No Node.js, o ciclo de eventos consiste em várias fases: timers, callbacks de I/O, idle, poll, check (setImmediate) e close. É importante distinguir entre microtasks (Promise, process.nextTick) e macrotasks (setTimeout, setInterval, I/O).
\n\nMicrotasks são executadas imediatamente após o término da fase atual, antes da próxima macrotask. Isso pode levar à "fome" do Event Loop se microtasks forem geradas infinitamente.
\n\n// Exemplo: microtasks bloqueiam macrotasks\nsetTimeout(() => console.log('Macrotask'), 0);\n\nPromise.resolve().then(() => {\n // Microtask infinita\n process.nextTick(() => {\n console.log('Microtask');\n // Chamada recursiva bloqueará o Event Loop\n process.nextTick(() => process.nextTick(() => {}));\n });\n});\n// Saída: 'Microtask' (infinitamente), 'Macrotask' nunca será executada\n\nPara controle avançado, use queueMicrotask() e setImmediate(). Lembre-se: process.nextTick tem prioridade sobre as microtasks de Promise.
2. Worker Threads: Multithreading Real no Node.js
\n\nO módulo worker_threads permite executar JavaScript em threads paralelas. Cada Worker tem sua própria instância V8 e memória isolada. A troca de dados ocorre por meio de mensagens ou SharedArrayBuffer.
Criando um Worker simples:
\n\n// main.js\nconst { Worker } = require('worker_threads');\n\nfunction runWorker(file, data) {\n return new Promise((resolve, reject) => {\n const worker = new Worker(file, { workerData: data });\n worker.on('message', resolve);\n worker.on('error', reject);\n worker.on('exit', (code) => {\n if (code !== 0) reject(new Error(`Worker parou com código ${code}`));\n });\n });\n}\n\n// Execução\nrunWorker('./worker.js', { iterations: 1e9 })\n .then(result => console.log('Resultado:', result))\n .catch(err => console.error(err));\n\n// worker.js\nconst { parentPort, workerData } = require('worker_threads');\n\nfunction heavyComputation(n) {\n let sum = 0;\n for (let i = 0; i < n; i++) { sum += Math.sqrt(i); }\n return sum;\n}\n\nparentPort.postMessage(heavyComputation(workerData.iterations));\n\nImportante: O Worker não tem acesso ao DOM, objetos globais do processo (process.env) ou módulos carregados na thread principal. Transmita apenas dados serializáveis.
\n\nPool de Workers
\nCriar um Worker para cada tarefa é custoso. Use um pool:
\n\nclass WorkerPool {\n constructor(file, size = 4) {\n this.workers = [];\n this.queue = [];\n for (let i = 0; i < size; i++) {\n const worker = new Worker(file);\n worker.on('message', (result) => this._handleResult(worker, result));\n worker.on('error', (err) => this._handleError(worker, err));\n this.workers.push(worker);\n }\n }\n\n runTask(data) {\n return new Promise((resolve, reject) => {\n this.queue.push({ data, resolve, reject });\n this._processQueue();\n });\n }\n\n _processQueue() {\n if (this.queue.length === 0) return;\n const worker = this.workers.find(w => !w.busy);\n if (!worker) return;\n worker.busy = true;\n const task = this.queue.shift();