DEV Community

Igor Melo
Igor Melo

Posted on • Updated on

Golang: Desmistificando channels - Exemplo concreto

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:

urls := []string{
    "http://files.com/file1.png",
    "http://files.com/file2.png",
    "http://files.com/file3.png",
}
Enter fullscreen mode Exit fullscreen mode

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.

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

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

for _, url := range urls {
    download(url)
    fmt.Println("concluído:", url)
}
Enter fullscreen mode Exit fullscreen mode

O resultado vai ser esse:

concluído: http://files.com/file1.png
concluído: http://files.com/file2.png
concluído: http://files.com/file3.png
Enter fullscreen mode Exit fullscreen mode

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
finished := make(chan string)
Enter fullscreen mode Exit fullscreen mode

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
func download(url string) {
    ...
}

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

    finished := make(chan string)

    for _, url := range urls {
        go download(url)
    }
}
Enter fullscreen mode Exit fullscreen mode

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
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
}
Enter fullscreen mode Exit fullscreen mode
  1. Recebemos os valores do channel
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
}
Enter fullscreen mode Exit fullscreen mode

Resultado:

concluído: http://files.com/file2.png
concluído: http://files.com/file1.png
concluído: http://files.com/file3.png
Enter fullscreen mode Exit fullscreen mode

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

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

Top comments (0)