Executando verificação de segurança...
2
uriel
6 min de leitura ·

O objeto Atomics do JS uma introdução, multithreading e compartilhamento de memória!

Nosso querido JavaScript é single threaded e, como tal, só pode executar uma coisa por vez; Guarde isso, isso é importante!

Portanto, se uma tarefa de execução longa bloquear o encadeamento principal do navegador, o navegador terá dificuldade para renderizar.

Por exemplo, se fôssemos percorrer e executar operações em uma grande estrutura de dados,
ou criar um loop simples muito grande.
Isso poderia fazer com que a renderização congelasse. Enquanto o loop não terminasse, ou enquanto o algoritmo estivesse percorrendo a grande estrutura de dados, não da pra fazer nada, apenas olhar e esperar.

Esse é um problema de single threaded.
Só da pra fazer uma coisa por vez e se ela toma muito tempo, não da pra fazer mais nada!

Não iremos falar sobre callbacks e promises neste texto.

Uma das soluções para esse problema é usar Web Workers.

Web Workers são mecanismos que permitem que uma operação de um dado script seja executado em uma thread diferente da thread principal da aplicação Web. Permitindo que cálculos laboriosos sejam processados sem que ocorra bloqueio da thread principal (geralmente associado à interface).
Fonte > mdn.

Sabemos que js é single threaded.
Com o tempo as aplicações no navegador estavam ficando cada vez mais exigentes.
Então resolveram trazer os web Workers para nos ajudar!

Agora aquele loop gigante pode ir pra outro thread e deixa nossa tela principal linda e leve sem ninguém atrapalhar!

Os Web Workers permitem a execução paralela no contexto do navegador.

No entanto como tudo em tecnologia, os Web Workers têm suas desvantagens; por exemplo, há um custo de transferência de dados de e para o thread Worker. Toda transferência é feita via postMessage.

O navegador usa o algoritmo structured clone
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm

Para duplicar um objeto, ou seja duplicação dos mesmos dados na memoria. Esse custo, dependendo das circunstâncias, pode superar o benefício da transferência para o thread Worker.

E se pudéssemos evitar a cópia de dados?
Uma maneira de contornar esse problema é aproveitar SharedArrayBuffer.

Para desenvolvedores de JavaScript, essa construção nos permite criar pedaços de memoria compartilhada. Em vez de copiar os dados do thread principal para o Worker e vice-versa, podemos atualizar a mesma memória compartilhada de ambos os lados.

// 
const length = 10;
 // Criando o tamanho do nosso buffer
const size = Int32Array.BYTES_PER_ELEMENT * length;
 // Criando o buffer com 10 inteiros 
const sharedBuffer = new SharedArrayBuffer(size);
const sharedArray = new Int32Array(sharedBuffer);

O que é Int32Array: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array

O que é sharedarraybuffer: https://developers.google.com/search/blog/2021/03/sharedarraybuffer-notes?hl=pt-br

Agora temos um pedaço de memória que pode ser compartilhado. Ou seja, o thread principal e os web workers podem acessar esse mesmo local juntos!

Isso é muito bom, economiza memória!
Mas como tudo em tecnologia, tem desvantagens!

Imagina o seguinte: Temos 3 ou mais Web Workers além do thread principal acessando um mesmo local de memória. Isso vai dar errado.
Muito errado. Podemos ter uma condição de corrida. Todos disputando o mesmo lugar ou termos um problema de atualizar 2 vezes o mesmo lugar com as mesmas informações.
Ou seja 2 workers podem fazer o mesmo trabalho ao mesmo tempo e isso é desperdício.

O que é condição de corrida: https://en.wikipedia.org/wiki/Race_condition

Então criaram o objeto Atomics.

As operações atômicas garantem que tenhamos uma maneira padronizada de ler e gravar dados em uma ordem previsível

Atomics gerencia o acesso de todo mundo ao pedaço de memória, assim impedindo condições de corrida, trabalhos desperdiçado, etc.

O que é Atomics:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics

// main.js
const worker = new Worker('worker.js');
const length = 10;
const size = Int32Array.BYTES_PER_ELEMENT * length;

const sharedBuffer = new SharedArrayBuffer(size);
const sharedArray = new Int32Array(sharedBuffer);
for (let i = 0; i < 10; i++) {
	Atomics.store(sharedArray, i, 0);
}
worker.postMessage(sharedBuffer);

Acessando o dados no worker

// worker.js
self.addEventListener('message', (event) => {
	const sharedArray = new Int32Array(event.data);
	for (let i = 0; i < 10; i++) {
		const arrayValue = Atomics.load(sharedArray, i);
		console.log(`The item at array index ${i} is ${arrayValue}`);
	}
}, false);


E se quiséssemos atualizar a matriz do trabalhador? Temos duas opções para essas atualizações usando o Atomics. Podemos usar store que vimos antes, ou podemos usar exchange. A diferença aqui é que storeretorna o valor que é armazenado e exchangeretorna o valor que é substituído. Vamos ver como isso funciona na prática:

// worker.js
self.addEventListener('message', (event) => {
	const sharedArray = new Int32Array(event.data);
	for (let i = 0; i < 10; i++) {
		if (i%2 === 0) {
			const storedValue = Atomics.store(sharedArray, i, 1);
			console.log(`The item at array index ${i} is now ${storedValue}`);
		} else {
			const exchangedValue = Atomics.exchange(sharedArray, i, 2);
			console.log(`The item at array index ${i} was ${exchangedValue}, now 2`);
		}
	}
}, false);

Agora podemos ler e atualizar a matriz do thread principal e do thread de trabalho. Atomics tem alguns outros métodos que podemos usar a nosso favor para gerenciar nossos novos arrays compartilhados. Dois dos métodos mais úteis são waite wake. waitnos permite esperar uma mudança em um índice de array e então continuar com as operações. Na prática, isso pode parecer algo assim no lado do trabalhador:

self.addEventListener('message', (event) => {
	const sharedArray = new Int32Array(event.data);
	const arrayIndex = 0;
	const expectedStoredValue = 50;
	// An optional 4th argument can be passed which is a timeout
	Atomics.wait(sharedArray, arrayIndex, expectedStoredValue);
	// Log the new value
	console.log(Atomics.load(sharedArray, arrayIndex));
}, false);

Aqui estamos esperando uma alteração em arrayIndex0, onde o valor armazenado esperado é 50. Então, podemos dizer a ele para acordar do thread principal quando alterarmos o valor no índice:

const newArrayValue = 100;
Atomics.store(sharedArray, 0, newArrayValue);
// The index that is being waited on
const arrayIndex = 0;
// The first agent waiting on the value
const queuePos = 1;
Atomics.wake(sharedArray, arrayIndex, queuePos);

Outras funções são fornecidas por conveniência, como adde subque adicionam ou subtraem do índice da matriz, respectivamente. Se você estiver interessado em operações bit a bit , algumas delas são fornecidas, incluindo or, ande xor.

Podemos ver que atomics podemos evitar condições de corrida e fazer atualizações previsíveis em um array usando os métodos de Atomics.

Para usar strings, precisamos convertê-las em uma representação numérica, por exemplo, um padrão conhecido para codificação como UTF-16.

function sharedArrayBufferToUtf16String(buf) {
	const array = new Uint16Array(buf);
	return String.fromCharCode.apply(null, array);
}

function utf16StringToSharedArrayBuffer(str) {
	// 2 bytes for each char
	const bytes = str.length *2;
	const buffer = new SharedArrayBuffer(bytes);
	const arrayBuffer = new Uint16Array(buffer);
	for (let i = 0, strLen = str.length; i < strLen; i++) {
		arrayBuffer[i] = str.charCodeAt(i);
	}
	return { array: arrayBuffer, buffer: buffer };
}

const exampleString = "Hello world, this is an example string!";
const sharedArrayBuffer = utf16StringToSharedArrayBuffer(exampleString).buffer;
const backToString = sharedArrayBufferToUtf16String(sharedArrayBuffer);

Todos os códigos são retirados deste texto:
https://www.sitepen.com/blog/the-return-of-sharedarraybuffers-and-atomics

Fiz uma pequena introdução ao objeto Atomics
lembrando que tudo isso **funciona no Node também! **
Se você quiser saber mais basta ler as fontes:

https://www.sitepen.com/blog/the-return-of-sharedarraybuffers-and-atomics

https://dev.to/feezyhendrix/worker-threads-in-node-js-2ikh

https://exploringjs.com/es2016-es2017/ch_shared-array-buffer.html

https://blogtitle.github.io/using-javascript-sharedarraybuffers-and-atomics/

https://blog.logrocket.com/understanding-sharedarraybuffer-and-cross-origin-isolation/

https://www.tutorialspoint.com/what-is-the-use-of-atomics-in-javascript

https://webreflection.medium.com/about-sharedarraybuffer-atomics-87f97ddfc098

https://www.geeksforgeeks.org/atomics-in-javascript/

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics

Carregando publicação patrocinada...