Deno + Multithreading: Somando números com Workers
Tags: deno, typescript, braziliandevs, tutorial
Published: 2022-08-07 03:42:14

Recentemente, tenho começado a estudar e experimentar um pouco do "novo" runtime de JavaScript / Typescript, o [Deno](https://deno.land).  

*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](https://dev.to/zanfranceschi/desafio-integracao-com-apis-4jco), acabei me deparando com a [Worker API](https://deno.land/manual/runtime/workers). 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](https://github.com/devtorello/bexchange), um novo desafio já havia sido lançado: [processar o conteúdo de um arquivo de forma distribuída](https://dev.to/zanfranceschi/desafio-processamento-distribuido-basico-45ka). 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:

```typescript
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:

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