SharedArrayBuffer em JavaScript
Este artigo explica o SharedArrayBuffer
em JavaScript.
Forneceremos uma explicação detalhada sobre os fundamentos do SharedArrayBuffer
, como utilizá-lo, casos de uso específicos e considerações de segurança.
YouTube Video
SharedArrayBuffer em JavaScript
O SharedArrayBuffer
é uma ferramenta poderosa em JavaScript para compartilhamento de memória entre várias threads. Especialmente em combinação com Web Workers, ele permite o processamento paralelo, tornando-o eficaz para tarefas intensivas em computação e aplicações que exigem capacidades em tempo real.
O que é SharedArrayBuffer?
O SharedArrayBuffer
fornece um buffer de memória em JavaScript que permite o compartilhamento de dados binários entre várias threads (principalmente Web Workers). Um ArrayBuffer
regular exige cópia entre a thread principal e os trabalhadores, mas o SharedArrayBuffer
permite o compartilhamento direto de memória sem cópia, melhorando significativamente o desempenho.
Recursos
- Memória Compartilhada Permite que múltiplas threads trabalhem com o mesmo espaço de memória.
- Melhoria de Desempenho Como a cópia pode ser omitida, a sobrecarga é reduzida ao processar grandes volumes de dados.
- Sincronização de Threads
Você pode usá-lo juntamente com
Atomics
para realizar a sincronização e evitar conflitos no acesso à memória.
Exemplo Básico de Uso
1// Create a 16-byte shared memory
2const sharedBuffer = new SharedArrayBuffer(16);
3
4// Treat it as an Int32Array
5const sharedArray = new Int32Array(sharedBuffer);
6
7// Set a value
8sharedArray[0] = 42;
9
10console.log(sharedArray[0]); // 42
Neste exemplo, criamos uma área de memória de 16 bytes usando o SharedArrayBuffer
e tratamos essa área de memória como um Int32Array
. Esse buffer de memória pode ser compartilhado entre várias threads.
Usando com Web Workers
O verdadeiro valor do SharedArrayBuffer
é demonstrado quando usado em combinação com Web Workers. O código a seguir é um exemplo do uso de memória compartilhada entre a thread principal e um trabalhador.
Na Thread Principal
1// Create a shared buffer
2const sharedBuffer = new SharedArrayBuffer(16);
3const sharedArray = new Int32Array(sharedBuffer);
4
5// Create a worker
6const worker = new Worker('worker.js');
7
8// Pass the shared buffer to the worker
9worker.postMessage(sharedBuffer);
10
11// Modify the memory
12// Output : Main thread: 100
13sharedArray[0] = 100;
14console.log("Main thread: ", sharedArray[0]);
No Lado do Trabalhador (worker.js
)
1// worker.js
2self.onmessage = function(event) {
3 // Use the received shared buffer
4 const sharedArray = new Int32Array(event.data);
5
6 // Read the contents of the memory
7 // Output : Worker thread: 100
8 console.log("Worker thread: ", sharedArray[0]);
9
10 // Change the value
11 sharedArray[0] = 200;
12};
- Neste exemplo, a thread principal cria um buffer compartilhado e o passa para o trabalhador. O trabalhador pode acessar esse buffer para ler e modificar valores. Dessa forma, os dados podem ser compartilhados entre threads sem cópia.
Confirmação de Atualização Bidirecional
Ao usar SharedArrayBuffer
, tanto a thread principal quanto os workers podem ler e escrever na mesma memória, permitindo a confirmação de atualização bidirecional. Abaixo está um exemplo onde a thread principal define um valor, um worker altera esse valor, e então a thread principal verifica a atualização.
Na Thread Principal
1// Create a shared buffer
2const sharedBuffer = new SharedArrayBuffer(16);
3const sharedArray = new Int32Array(sharedBuffer);
4
5// Create a worker
6const worker = new Worker('worker.js');
7
8// Pass the shared buffer to the worker
9worker.postMessage(sharedBuffer);
10
11// Set initial value
12// Output : Main thread initial: 100
13sharedArray[0] = 100;
14console.log("Main thread initial:", sharedArray[0]);
15
16// Listen for worker confirmation
17worker.onmessage = () => {
18 // Output : Main thread after worker update: 200
19 console.log("Main thread after worker update:", sharedArray[0]);
20};
No Lado do Trabalhador (worker.js
)
1// worker.js
2self.onmessage = function(event) {
3 const sharedArray = new Int32Array(event.data);
4
5 // Read initial value
6 // Output : Worker thread received: 100
7 console.log("Worker thread received:", sharedArray[0]);
8
9 // Update the value
10 sharedArray[0] = 200;
11
12 // Notify main thread
13 self.postMessage("Value updated");
14};
- Neste exemplo, a thread principal escreve o valor
100
primeiro, e depois que o worker lê esse valor, ele o reescreve para200
. Após isso, o worker notifica a thread principal, e a thread principal lê novamente a memória compartilhada para confirmar a atualização. Dessa forma, combinando notificações, é possível confirmar a atualização de forma bidirecional.
Sincronização com Atomics
Ao usar memória compartilhada, é necessário ter cuidado com condições de competição de dados e inconsistências. Quando múltiplos threads acessam a mesma memória simultaneamente, podem ocorrer conflitos. Para evitar isso, o JavaScript utiliza o objeto Atomics
para sincronização.
Por exemplo, para incrementar com segurança um contador com múltiplos threads, você pode usar Atomics
para evitar conflitos.
1const sharedBuffer = new SharedArrayBuffer(16);
2const sharedArray = new Int32Array(sharedBuffer);
3
4// Increment the counter
5Atomics.add(sharedArray, 0, 1);
6
7console.log(Atomics.load(sharedArray, 0)); // 1
Atomics.add
incrementa o valor em um índice específico de forma atômica e retorna o novo valor. Essa operação é garantida para ser livre de conflitos com outros threads. Atomics.load
também é utilizado para ler valores de maneira segura da memória compartilhada.
Espera e Notificação Usando Atomics.wait
e Atomics.notify
Ao usar SharedArrayBuffer
, há situações em que um worker precisa esperar até que uma certa condição seja atendida, e então outro worker deve notificar o worker em espera após cumprir essa condição. Nesses casos, Atomics.wait
e Atomics.notify
são úteis.
Atomics.wait
bloqueia uma thread até que o valor em um índice específico da memória compartilhada mude, enquanto Atomics.notify
notifica as threads em espera de que elas podem continuar. Isso permite espera e notificação seguras entre múltiplos workers. No entanto, Atomics.wait
não pode ser usado na thread principal e está disponível apenas dentro de workers.
1// Create a shared buffer (1 Int32 slot is enough for signaling)
2const sharedBuffer = new SharedArrayBuffer(4);
3const sharedArray = new Int32Array(sharedBuffer);
4
5// Create workers with names
6const waiter = new Worker('worker.js', { name: 'waiter' });
7const notifier = new Worker('worker.js', { name: 'notifier' });
8
9// Pass the shared buffer to both
10waiter.postMessage(sharedBuffer);
11notifier.postMessage(sharedBuffer);
12
13// Listen for messages
14waiter.onmessage = (event) => {
15 console.log(`[Main] Message from waiter:`, event.data);
16};
17notifier.onmessage = (event) => {
18 console.log(`[Main] Message from notifier:`, event.data);
19};
- Na thread principal, um
SharedArrayBuffer
é criado como memória compartilhada e é convertido em umInt32Array
com apenas um elemento. Esse único espaço inteiro é usado como um sinal para sincronizar entre os workers. Em seguida, dois workers são criados, e cada um recebe uma função usando a propriedadename
:waiter
(papel de espera) enotifier
(papel de notificação). Por fim, o buffer compartilhado é passado para ambos os workers, e manipuladoresonmessage
são configurados para que as mensagens enviadas de cada worker possam ser recebidas.
No Lado do Trabalhador (worker.js
)
1// worker.js
2onmessage = (event) => {
3 const sharedArray = new Int32Array(event.data);
4
5 if (self.name === 'waiter') {
6 postMessage('Waiter is waiting...');
7 // Wait until notifier signals index 0
8 Atomics.wait(sharedArray, 0, 0);
9 postMessage('Waiter was notified!');
10 }
11
12 if (self.name === 'notifier') {
13 postMessage('Notifier is preparing...');
14 setTimeout(() => {
15 // Notify waiter after 2 seconds
16 Atomics.store(sharedArray, 0, 1);
17 Atomics.notify(sharedArray, 0, 1);
18 postMessage('Notifier has sent the signal!');
19 }, 2000);
20 }
21};
22// Output
23// [Main] Message from waiter: Waiter is waiting...
24// [Main] Message from notifier: Notifier is preparing...
25// [Main] Message from notifier: Notifier has sent the signal!
26// [Main] Message from waiter: Waiter was notified!
- Neste exemplo, o worker
waiter
permanece em estado de espera usandoAtomics.wait
enquanto o valor no índice0
for0
. Por outro lado, quando o workernotifier
altera o valor para123
comAtomics.store
e chamaAtomics.notify
, o workerwaiter
será retomado e poderá obter o valor atualizado. Com isso, pode-se alcançar uma espera e notificação eficiente e segura entre threads.
Casos de Uso para SharedArrayBuffer
SharedArrayBuffer
é especialmente útil para os seguintes casos de uso:.
- Processamento em Tempo Real É adequado para aplicações que requerem baixa latência, como processamento de áudio e vídeo ou motores de jogos, onde os dados precisam ser compartilhados instantaneamente entre as threads.
- Computação Paralela
Ao processar grandes quantidades de dados simultaneamente com múltiplas threads, o uso de
SharedArrayBuffer
evita cópias de memória e pode melhorar o desempenho. - Aprendizado de Máquina Ao paralelizar tarefas como pré-processamento de dados e inferência, torna-se possível uma computação eficiente.
Considerações de Segurança
SharedArrayBuffer
é um recurso poderoso, mas também apresenta riscos de segurança. Em particular, preocupações sobre ataques de canal lateral como o Spectre
suspenderam temporariamente seu suporte. Para mitigar essa vulnerabilidade, os navegadores implementaram as seguintes medidas:.
- Isolamento de Site
Sites que permitem o uso de
SharedArrayBuffer
serão executados em um processo completamente isolado de outros sites. - Política de Recursos Entre Origens
Para usar o
SharedArrayBuffer
, os cabeçalhosCross-Origin-Opener-Policy
eCross-Origin-Embedder-Policy
devem ser configurados corretamente.
Por exemplo, ao configurar cabeçalhos como os seguintes, o uso de SharedArrayBuffer
se torna possível:.
1Cross-Origin-Opener-Policy: same-origin
2Cross-Origin-Embedder-Policy: require-corp
Isso evita que recursos externos interfiram no conteúdo atual e aumenta a segurança.
Resumo
SharedArrayBuffer
é uma ferramenta muito poderosa para compartilhar memória entre múltiplos threads. É uma tecnologia essencial para melhorar o desempenho, e seus efeitos são particularmente evidentes nos campos de processamento em tempo real e computação paralela. No entanto, também envolve riscos de segurança, por isso a configuração e a sincronização corretas são importantes.
Ao utilizar SharedArrayBuffer
, você pode construir aplicações web mais avançadas e com maior desempenho.
Você pode acompanhar o artigo acima usando o Visual Studio Code em nosso canal do YouTube. Por favor, confira também o canal do YouTube.