Golang: Desmistificando channels - Exemplo concreto
Tags: programming, go, tutorial, braziliandevs
Published: 2022-10-01 21:17:00

## Exemplo concreto de uso de channels

Vamos supor que você tenha um slice de URLs de arquivos e queira baixar todos de forma simultânea.

Você pode começar implementando uma solução sequencial, e depois fazer isso concorrente com goroutines, e por último usar os channels para esperar cada download terminar e comunicar que eles finalizaram.

### Fazendo os downloads de forma sequencial

Digamos que você tenha um slice com os arquivos que quer baixar:

```go
urls := []string{
	"http://files.com/file1.png",
	"http://files.com/file2.png",
	"http://files.com/file3.png",
}
```

Para fazer o download deles, você pode criar uma função `download` que recebe a URL do arquivo e faz o download.

Para simular o download, vamos fazer uma função que espera um tempo aleatório entre 0 e 3 segundos.

```go
func download(url string) {
	r := rand.Intn(4)
	time.Sleep(time.Duration(r) * time.Second)
}
```

Agora, vamos fazer o download de cada arquivo de forma sequencial:

```go
for _, url := range urls {
	download(url)
	fmt.Println("concluído:", url)
}
```

O resultado vai ser esse:

```bash
concluído: http://files.com/file1.png
concluído: http://files.com/file2.png
concluído: http://files.com/file3.png
```

No pior cenário o programa vai levar 3 segundos por download, ou seja, um total de 9 segundos.

Se conseguirmos fazer isso de forma simultânea, o pior cenário vai levar 3 segundos no total.

### Usando goroutines para fazer downloads concorrentes

1. Criamos um channel de string

```go
finished := make(chan string)
```

Poderia ser de outro tipo, mas nesse caso vou mandar a URL do download que finalizou, por isso vou usar um `chan string`.

1. Iniciar o download numa nova goroutine

```go
func download(url string) {
	...
}

func main () {
	urls := []string{
		...
	}

	finished := make(chan string)
	
	for _, url := range urls {
		go download(url)
	}
}
```

Isso não vai funcionar, porque a goroutine `main` vai finalizar primeiro e vai encerrar o programa.

1. Quando o download finalizar, vamos enviar para o channel a URL

```go
func download(url string) {
	...
}

func downloadAndNotify(url string, ch chan string) {
	download(url)

	// agora em vez de só baixar em outra goroutine, estamos
	// enviando a url do download finalizado para o channel
	ch <- url
}

func main () {
	...
	
	for _, url := range urls {
		go downloadAndNotify(url, finished)
	}

	// obs.: o programa vai continuar encerrando antes de fazer os downloads
}
```

1. Recebemos os valores do channel

```go
func main () {
	...
	
	for _, url := range urls {
		go downloadAndNotify(url, finished)
	}

	// sabemos que o números de resultados vai ser igual ao 
	// tamanho de urls, então podemos fazer esse for para repetir len(urls) vezes
	for range urls {

		// recebemos um resultado do channel
		// lembre-se, esse receber vai bloquear até que alguma goroutine envie algo
		url := <-finished

		fmt.Println("concluído:", url)
	}

	// como o loop anterior repete 3x, todos os resultados das 3 goroutines
	// são lidos, e a main finaliza quando todos os downloads terminam
}
```

Resultado:

```bash
concluído: http://files.com/file2.png
concluído: http://files.com/file1.png
concluído: http://files.com/file3.png
```

Podemos também usar uma função anônima no lugar do `downloadAndNotify`, e o código completo ficaria assim:

```go
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func download(url string) {
	r := rand.Intn(4)
	time.Sleep(time.Duration(r) * time.Second)
}

func main() {
	urls := []string{
		"http://files.com/file1.png",
		"http://files.com/file2.png",
		"http://files.com/file3.png",
	}

	finished := make(chan string)

	for _, url := range urls {
		// isso é necessário agora porque estamos usando uma closure
		url := url

		go func() {
			download(url)
			finished <- url
		}()
	}

	for range urls {
		url := <-finished
		fmt.Println("concluído:", url)
	}
}
```