DEV Community 👩‍💻👨‍💻

Tutorial Livre
Tutorial Livre

Posted on • Updated on

Golang: Desmistificando channels - Range e close

Usando range e close

Se você tiver um canal, você pode receber mensagens usando um for range, parecido com percorrer um array ou slice.

Só que a diferença é que um for range em um canal vai ficar repetindo até o canal ser fechado. Se o canal nunca for fechado, isso vai fazer um loop infinito.

ch := make(chan struct{})

// loop infinito porque o channel nunca é fechado
for val := range ch {
}
Enter fullscreen mode Exit fullscreen mode

Para fechar um canal é muito simples, porém deve ser feito com cuidado, porque se você fechar um canal e tentar enviar isso vai causar um panic.

Então tem duas regras básicas que você pode seguir para fechar um canal:

  1. Você deve fechar na função que envia, porque só ela sabe quando pode fechar
  2. Você deve garantir que está fechando depois de todas as goroutines terem terminado de enviar

Não tem problema tentar ler de um channel fechado em um range:

ch := make(chan int)
close(ch)

for val := range ch {
    // não vai entrar aqui porque não tem nada para ser lido e não vai
    // repetir porque o channel está fechado
}
Enter fullscreen mode Exit fullscreen mode

Você pode seguramente fechar um channel depois de ter enviado todos os valores:

func process(values []int, results chan string) {

    // neste caso eu estou usando WaitGroup pra garantir que todas as
    // goroutines já escreveram no channel antes de poder fechá-lo
    wg := sync.WaitGroup{}
    wg.Add(len(values))

    // faço um processamento concorrente de values
    for _, val := range values {
        go func() {
            ...
        }()
    }

    // quando todas as goroutines terminarem, passa do Wait
    wg.Wait()

    // todas as goroutines já finalizaram, posso seguramente fechar o canal
    close(results)
}
Enter fullscreen mode Exit fullscreen mode

Fazendo múltiplas goroutines esperarem por um sinal

Uma coisa que você pode querer fazer é notificar uma goroutine que ela pode começar a fazer algum tipo de processamento.
Isso é bem fácil... Basta usar um channel e enviar um valor. Quando ela receber é porque ela pode iniciar.

Exemplo:

func main() {
    // esse channel serve para saber se o nosso servidor está pronto ou não
    ready := make(chan bool)

    go func() {
        startHttpServer(3000)
        ready <- true
    }()

    // fazer coisas que não dependam do servidor rodando
    // ...

    // esperar o servidor iniciar
    <-ready

    // fazer uma requisição para o servidor, agora que podemos garantir que ele está rodando
    client.Get("http://localhost:3000/")
}
Enter fullscreen mode Exit fullscreen mode

Isso até funciona se tivermos só uma goroutine esperando o servidor iniciar.

Mas e se quisermos ter várias esperando esse sinal, como resolver isso?
Enviar um valor ao channel para cada uma não é uma solução viável.

Para isso podemos fazer um truque esperto que é usar o close do channel.

Em vez de enviarmos um valor para o channel para notificar que o servidor iniciou, podemos simplesmente fechar o channel e todas as goroutines que estiverem bloqueadas tentando receber do channel vão ser desbloqueadas.

func main() {
    ready := make(chan bool)

    go func() {
        startHttpServer(3000)
        close(ready) // usando close em vez de enviar
    }()

    // esperar o servidor iniciar
    <-ready

    // fazer uma requisição para o servidor, agora que podemos garantir que ele está rodando
    client.Get("http://localhost:3000/")
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.