Deno + Multithreading: Somando números com Workers
Tatiana Vitorello
Posted on August 7, 2022
Recentemente, tenho começado a estudar e experimentar um pouco do "novo" runtime de JavaScript / Typescript, o Deno.
Disclaimer/off: e estou apaixonada, diga-se de passagem. Não só pelo fato do mascote ser um dinossaurinho, mas por toda a rapidez e facilidade que ganhamos se comparado ao node. 🤪
Enquanto avaliava o opções para executar um desafio proposto pelo Zanfranceschi, acabei me deparando com a Worker API. Pensei em utilizá-lo para o desafio, mas senti que essa não era a melhor opção para solucionar o problema.
No mesmo dia que terminei o hexchange, um novo desafio já havia sido lançado: processar o conteúdo de um arquivo de forma distribuída. Esse seria um cenário "perfeito" para testar os workers - apesar de não ser necessariamente um processamento distribuído, e sim multithread.
Tendo isso em mente, eu tentei bolar uma implementação "simples". Vamos lá! Mas antes de começar...
Como funcionam os Workers no Deno?
Os workers, em poucas palavras, permitem que uma implementação seja executada em várias threads diferentes. Cada instância de um worker é executada em uma thread dedicada unicamente para o mesmo.
Para sua execução, é necessário ter um parent e um arquivo que irá executar a lógica de processamento, denominado como worker.
O Parent
O parent ficará responsável por criar a lógica para a instanciação dos workers e, também, do recebimento dos resultados do seu processamento.
Para podermos solicitar a execução de um worker, mandamos uma mensagem através do método postMessage
, que deve conter todos os dados a serem processados.
No caso do desafio proposto, eu precisava solicitar a soma de uma linha com 200 números, então a implementação ficou mais ou menos assim:
const createWorkerURL = (filename: string): string => new URL(filename, import.meta.url).href;
export function sumDistributedFileNumbers(line: string, name: string): Promise<number> {
return new Promise((resolve) => {
const worker = new Worker(createWorkerURL('./workers.ts'), { type: 'module' });
worker.addEventListener('message', (message) => {
console.log(`Total of ${message.data.sum} returned from ${message.data.name}`);
resolve(message.data.sum);
});
worker.postMessage({ line, name });
});
}
Nesse caso, além da implementação comum, utilizei mais duas coisas: Promises
e o método addEventListener
do próprio Worker.
No caso da promise, precisei utilizá-la para garantir que quando minha função retornasse alguma coisa, ela já retornasse a soma de todos os números daquela linha. Já o método addEventListener
, utilizei para poder receber a soma total da linha - que seria enviado através do worker para o parent.
Note que para a criação dos workers, precisamos passar a referência do arquivo onde a lógica se encontra.
Os Workers
Ainda seguindo no raciocínio do desafio proposto, dentro dos workers eu teria que ler uma linha inteira contendo 200 números no total. Para chegar nesse resultado, essa foi a lógica do arquivo dos workers:
type MessageContent = {
line: string;
name: string;
};
self.onmessage = ({ data: { line, name } }: MessageEvent<MessageContent>): void => {
const sum = line
.split(' ')
.map((num) => Number(num))
.reduce((a, b) => {
return a + b;
});
self.postMessage({ sum, name });
self.close();
};
Dessa forma, a soma é realizada e, por fim, o resultado da soma é enviado de volta para o parent.
Resultado Final
Para consolidar o resultado final, ou seja, todas as somas parciais geradas pelos workers, o arquivo main.js
recebe uma série de promises e as executa através do método Promise.all
. Em uma execução de sucesso, receberemos algo parecido:
Total of 52499 returned from worker-1298
Total of 53023 returned from worker-1427
Total of 46830 returned from worker-1428
Total of 48380 returned from worker-1557
Total of 44707 returned from worker-1558
Total of 53450 returned from worker-1687
Total of 53031 returned from worker-1688
Total of 50385 returned from worker-1817
Total of 52455 returned from worker-1818
Total of 50101 returned from worker-1947
Total of 51728 returned from worker-1948
File total sum: 102367758!
Processing finished with 17589.7198 ms.
Com a complexidade O(n²), a aplicação leva em média ~18 segundos para executar a soma de 20.000 linhas de um arquivo, considerando que existam 200 números por linha.
O próximo passo será executar ~de fato~ o exercício seguindo a premissa de processamento distribuído e, por fim, compará-lo com este.
Disclaimers
Gostaria de agradecer ao @zanfranceschi por me incentivar a dar o primeiro passo com a criação de conteúdo através dos seus desafios! Muito obrigada! 😁
Até a próxima! 🤗
Posted on August 7, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.