Golang: Desmistificando channels - Range e close

igormelo

Igor Melo

Posted on October 1, 2022

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 receber valores até o canal ser fechado. Se o canal nunca for fechado, o loop vai ficar bloqueado.

ch := make(chan struct{})

for val := range ch {
  // essa linha nunca é executada, porque val nunca é recebido
  doSomething()
}
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() {
            results <- val * val
        }()
    }

    wg.Wait()
    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
💖 💪 🙅 🚩
igormelo
Igor Melo

Posted on October 1, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related