DEV Community

Tatiana Vitorello
Tatiana Vitorello

Posted on

Deno + Multithreading: Somando números com Workers

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 });
    });
}
Enter fullscreen mode Exit fullscreen mode

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();
};
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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! 🤗

Top comments (2)

Collapse
 
urielsouza29 profile image
Uriel dos Santos Souza • Edited

Que legal, primeiro texto em pt-BR que leio sobre DENO com algo mais pratico.

Tenho visto que Deno é legal, mas muito lento perante Node.
Principalmente nessas APIs que primeiro foram implementadas no NODE.
Depois especificadas para WEB.

Será que o Deno vai conseguir melhorar bem o desempenho delas para competir?

Espero mais textos com Deno, quem sabe você possa ser referencia em Deno
no Brasil.

Quem começa antes tem mais chances. :)

Collapse
 
zanfranceschi profile image
Francisco Zanfranceschi • Edited

Oh, meu deus... tenho nem roupa pra uma coisa dessas! Eu que te agradeço! Vc é muito foda, amiga! Parabéns!!!