⚡ Curso de HTML & HTML5 • Web-ZoneWare

HTML5 Web Workers: Multithreading Prático | Web Academy

Lição 39 de 50

HTML5 Web Workers: Multithreading Prático | Web Academy

Tradicionalmente, os navegadores web executam o código JavaScript em uma thread única (a chamada Main Thread ou Thread de UI). Nessa arquitetura, o mesmo motor encarregado de processar seus scripts também é responsável por redesenhar a tela, renderizar estilos CSS e responder a cliques e eventos de rolagem do usuário. Se você disparar uma operação pesada nessa thread, a página inteira simplesmente congela. A introdução dos HTML5 Web Workers quebrou essa barreira, trazendo o verdadeiro multithreading para a web e permitindo rodar cálculos intensivos em segundo plano com fluidez total.

1. O Problema do Congelamento da Interface (Jank)

Imagine que sua aplicação precise ordenar uma matriz com 500 mil registros, calcular filtros matemáticos sobre uma foto em alta definição ou processar um arquivo volumoso.

Se esse processamento for executado na thread principal:

  • As animações CSS e transições de tela caem de 60 FPS para zero.
  • O cursor do mouse para de responder e os botões parecem quebrados.
  • Após alguns segundos, o navegador exibe a temida janela de alerta: “Esta página não está respondendo. Deseja aguardar ou fechar?”.

💡 A Solução com Web Workers

Um Web Worker é uma linha de execução de segundo plano independente gerenciada pelo próprio sistema operacional. Ele opera em paralelo à interface visual: enquanto o Worker consome 100% de um núcleo de CPU triturando dados complexos, a página principal continua rolando suavemente a 60 quadros por segundo sem qualquer engasgo.

2. O Que um Web Worker Pode e NÃO Pode Acessar

Para evitar problemas clássicos de concorrência e corrupção de memória (como race conditions ou deadlocks), os Web Workers operam sob um modelo de isolamento estrito:

Recurso Acesso Permitido no Worker? Motivo Técnico
Árvore do DOM (window, document) ❌ Proibido O DOM não é seguro para operações concorrentes simultâneas de múltiplas threads.
Rede e Comunicação (fetch, XMLHttpRequest, WebSocket) ✅ Permitido Permite baixar e processar grandes pacotes de dados diretamente em background.
Temporizadores (setTimeout, setInterval) ✅ Permitido Controle temporal padrão do ambiente JavaScript.
Bancos de Dados Locais (IndexedDB) ✅ Permitido Permite ler e gravar gigabytes de dados no cliente sem bloquear a tela.
Importação de Módulos (importScripts()) ✅ Permitido Permite importar bibliotecas externas para o contexto do worker.

3. O Mecanismo de Troca de Mensagens (postMessage)

Como a página principal e o Worker habitam universos isolados, toda a comunicação é feita através de passagem de mensagens serializadas:

JavaScript 20 linhas
// === NA PÁGINA PRINCIPAL (main.js) ===
 
// 1. Cria a thread dedicada apontando para o arquivo do worker
const meuWorker = new Worker('calculadora-worker.js');
 
// 2. Envia uma carga de dados para o worker processar
meuWorker.postMessage({ numeroInicial: 5000000 });
 
// 3. Ouve a resposta enviada de volta pelo worker
meuWorker.onmessage = function(evento) {
    console.log('Resultado recebido do Worker:', evento.data.resultado);
};
 
// 4. Captura eventuais erros de execução
meuWorker.onerror = function(erro) {
    console.error('Erro no Worker:', erro.message);
};
 
// 5. Encerra a thread quando não for mais necessária
// meuWorker.terminate();
JavaScript 22 linhas
// === DENTRO DO WORKER (calculadora-worker.js) ===
 
// O escopo global do worker é acessado via 'self'
self.onmessage = function(evento) {
    const limite = evento.data.numeroInicial;
    let totalPrimos = 0;
 
    // Cálculo matemático intensivo que demoraria segundos
    for (let i = 2; i <= limite; i++) {
        let primo = true;
        for (let j = 2; j * j <= i; j++) {
            if (i % j === 0) {
                primo = false;
                break;
            }
        }
        if (primo) totalPrimos++;
    }
 
    // Devolve o resultado para a thread principal
    self.postMessage({ resultado: totalPrimos });
};

4. Código Prático Completo: Demonstração Interativa Sem Travamento

Para facilitar testes imediatos sem a necessidade de criar múltiplos arquivos no servidor, podemos gerar o Worker dinamicamente em memória através de um Blob URL:

HTML5 10 linhas
<div class="wzw-worker-demo">
    <h3>⚡ Demonstração de Concorrência Real com Web Worker</h3>
    
    <!-- Elemento com animação CSS contínua para provar a fluidez visual -->
    <div class="spinner-teste"></div>
    <p><small>Observe como este ícone gira sem qualquer interrupção durante o cálculo pesado.</small></p>
 
    <button id="btnCalcular" class="btn-worker">Calcular Números Primos em Background</button>
    <div id="painelStatus" class="status-box">Aguardando comando...</div>
</div>
CSS3 41 linhas
/* Animação que provará a ausência de congelamento da UI */
@keyframes girarSuave {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}
 
.spinner-teste {
    width: 44px;
    height: 44px;
    margin: 12px 0;
    border: 5px solid #334155;
    border-top-color: #38bdf8;
    border-radius: 50%;
    animation: girarSuave 1s linear infinite;
}
 
.wzw-worker-demo {
    padding: 24px;
    background-color: #0f172a;
    border: 1px solid #1e293b;
    border-radius: 8px;
    color: #f8fafc;
}
 
.btn-worker {
    padding: 12px 20px;
    background-color: #10b981;
    color: #fff;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    font-weight: 600;
}
 
.status-box {
    margin-top: 15px;
    padding: 12px;
    background-color: #1e293b;
    border-radius: 6px;
    font-family: monospace;
}
JavaScript 33 linhas
// Código do Worker encapsulado em string para teste imediato
const codigoWorker = `
    self.onmessage = function(e) {
        const n = e.data;
        let contagem = 0;
        // Laço pesado proposital
        for (let i = 0; i < n; i++) {
            contagem += Math.sqrt(i);
        }
        self.postMessage('Cálculo concluído com sucesso: ' + Math.round(contagem));
    };
`;
 
// Criação do Worker inline via Blob
const blob = new Blob([codigoWorker], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(blob);
const workerInline = new Worker(workerUrl);
 
const btn = document.getElementById('btnCalcular');
const status = document.getElementById('painelStatus');
 
btn.addEventListener('click', () => {
    status.textContent = '⏳ Executando cálculo massivo em thread separada... A animação continua a 60 FPS!';
    btn.disabled = true;
 
    // Dispara a carga de trabalho para a thread paralela
    workerInline.postMessage(20000000);
});
 
workerInline.onmessage = function(evento) {
    status.textContent = '✅ ' + evento.data;
    btn.disabled = false;
};
🧪 Quer testar o Web Worker e multithreading no seu navegador agora?
⚡ Abrir no Playground IDE →

5. Transferência de Dados com Zero Latência (Transferable Objects)

Por padrão, o método postMessage() realiza uma cópia estruturada (structured clone) dos objetos. Para matrizes gigantes ou buffers de imagem com dezenas de megabytes gerados via Canvas, duplicar a memória pode causar lentidão momentânea.

Para resolver isso, o HTML5 suporta Transferable Objects (como instâncias de ArrayBuffer), onde a posse da memória é transferida instantaneamente para o Worker sem cópia física:

JavaScript 7 linhas
// Cria um buffer de 32 Megabytes
const bufferGigante = new ArrayBuffer(32 * 1024 * 1024);
 
// O segundo argumento transfere a posse com latência zero (Zero-Copy)
meuWorker.postMessage(bufferGigante, [bufferGigante]);
 
// Na thread principal, bufferGigante.byteLength agora vale 0!

6. Ferramentas Relacionadas & Próximos Passos

Continue explorando as tecnologias avançadas de processamento e dados da nossa suíte:

7. Referências Técnicas & Especificações Oficiais


Rolar para cima