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",
}
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)
}
Agora, vamos fazer o download de cada arquivo de forma sequencial:
for _, url := range urls {
download(url)
fmt.Println("concluído:", url)
}
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
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
- Criamos um channel de string
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
.
- 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)
}
}
Isso não vai funcionar, porque a goroutine main
vai finalizar primeiro e vai encerrar o programa.
- 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
}
- 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
}
Resultado:
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:
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)
}
}
Top comments (0)