Golang: Desmistificando channels - Range e close
Igor Melo
Posted on October 1, 2022
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()
}
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:
- Você deve fechar na função que envia, porque só ela sabe quando pode fechar
- 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
}
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)
}
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/")
}
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/")
}
Posted on October 1, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.