SharedArrayBuffer: O Que É e Por Que Usar
SharedArrayBuffer é um objeto JavaScript que permite compartilhar um buffer de memória entre múltiplos workers (threads). Diferentemente dos Web Workers comuns, que comunicam-se por cópia de dados (postMessage), o SharedArrayBuffer oferece acesso direto à mesma região de memória, eliminando overhead de serialização. Isso é essencial em aplicações CPU-intensivas, processamento de vídeo, simulações 3D e análise de grandes datasets em tempo real.
A principal vantagem é o desempenho: múltiplos workers podem ler e modificar os mesmos dados simultaneamente sem cópias custosas. Porém, com grande poder vem grande responsabilidade — você herda problemas clássicos de programação paralela como race conditions e deadlocks. É aqui que entra a API Atomics.
Configurando SharedArrayBuffer
Criação Básica
Para criar um SharedArrayBuffer, você instancia-o como um TypedArray. Diferentemente de ArrayBuffer comum, este é compartilhável entre contextos:
// main.js
const sharedBuffer = new SharedArrayBuffer(32); // 32 bytes
const sharedArray = new Int32Array(sharedBuffer);
// Inicializa dados
sharedArray[0] = 100;
// Cria e passa para worker
const worker = new Worker('worker.js');
worker.postMessage({ sharedBuffer });
// worker.js
self.onmessage = (event) => {
const { sharedBuffer } = event.data;
const sharedArray = new Int32Array(sharedBuffer);
console.log('Valor inicial:', sharedArray[0]); // 100
sharedArray[0] = 200; // Modifica diretamente na memória compartilhada
};
Restrições de Segurança
Por questões de segurança (mitigação de Spectre), navegadores modernos exigem headers específicos. O servidor deve retornar:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Atomics: Operações Thread-Safe
A API Atomics fornece métodos que garantem operações indivisíveis (atômicas) sobre SharedArrayBuffer, prevenindo race conditions. Os principais são load, store, compareExchange, wait e notify.
Operações Básicas Atômicas
// main.js
const sharedBuffer = new SharedArrayBuffer(8);
const sharedArray = new Int32Array(sharedBuffer);
// Escrita atômica
Atomics.store(sharedArray, 0, 42);
// Leitura atômica
const value = Atomics.load(sharedArray, 0);
console.log(value); // 42
// Compare-and-swap (atualiza se valor == esperado)
const wasUpdated = Atomics.compareExchange(sharedArray, 0, 42, 100);
console.log(wasUpdated); // 42 (valor anterior)
console.log(Atomics.load(sharedArray, 0)); // 100
Sem Atomics, leituras/escritas simples (sharedArray[0] = 42) não são garantidas como thread-safe em todas as arquiteturas. Atomics garante que a operação completa atomicamente antes de qualquer outra instrução interferir.
Wait e Notify: Sincronização Entre Threads
O padrão mais poderoso é usar wait() e notify() para sincronização produtor-consumidor:
// main.js (produtor)
const sharedBuffer = new SharedArrayBuffer(4);
const sharedArray = new Int32Array(sharedBuffer);
const worker = new Worker('worker.js');
worker.postMessage({ sharedBuffer });
// Simula produção
setTimeout(() => {
Atomics.store(sharedArray, 0, 42);
Atomics.notify(sharedArray, 0); // Acorda workers aguardando
}, 1000);
// worker.js (consumidor)
self.onmessage = (event) => {
const { sharedBuffer } = event.data;
const sharedArray = new Int32Array(sharedBuffer);
console.log('Esperando dados...');
Atomics.wait(sharedArray, 0, 0); // Bloqueia até notify ou timeout
const value = Atomics.load(sharedArray, 0);
console.log('Recebi:', value); // 42
};
Atomics.wait() só funciona em Workers, nunca na thread principal (evita congelamento da UI). Quando notify() é chamado, todos os workers aguardando aquele índice são acordados.
Caso de Uso Prático: Pipeline de Processamento
Imagine processar frames de vídeo com múltiplos workers. Cada worker processa um quadro diferentes enquanto compartilham estado de progresso:
// main.js
const FRAME_COUNT = 100;
const sharedBuffer = new SharedArrayBuffer(4 * 3); // 3 Int32s
const state = new Int32Array(sharedBuffer); // [framesProcessadas, erros, pausado]
// Inicia 4 workers
const workers = Array.from({ length: 4 }, () => {
const w = new Worker('frame-processor.js');
w.postMessage({ sharedBuffer, totalFrames: FRAME_COUNT });
return w;
});
// Monitor progress
setInterval(() => {
const processed = Atomics.load(state, 0);
console.log(`Progresso: ${processed}/${FRAME_COUNT}`);
}, 500);
// frame-processor.js
self.onmessage = (event) => {
const { sharedBuffer, totalFrames } = event.data;
const state = new Int32Array(sharedBuffer);
for (let i = 0; i < totalFrames; i++) {
// Processa frame
processFrame(i);
// Incremento atômico do contador
Atomics.add(state, 0, 1);
}
};
function processFrame(index) {
// Simulação: processamento pesado
let sum = 0;
for (let i = 0; i < 1000000; i++) sum += Math.sqrt(i);
}
Conclusão
SharedArrayBuffer e Atomics são ferramentas poderosas para paralelismo real em JavaScript. Primeira aprendizagem: SharedArrayBuffer elimina cópias de dados, permitindo que múltiplos workers acessem a mesma memória. Segunda: Atomics garante operações thread-safe e fornece primitivas de sincronização (wait/notify). Terceira: use esse padrão apenas quando necessário — overhead de sincronização pode degradar performance em casos triviais. Domine esses conceitos e você dominará computação paralela em JavaScript.