No olá mundo o canal levou uma mensagem só, da gorrotina para a main.
Mas quase nunca é uma mensagem só. São as linhas de um arquivo, as páginas de uma API, os eventos de uma fila. E é aqui que entra o primeiro padrão da série.
O que é um gerador Link para o cabeçalho
Um gerador é uma função que dispara uma gorrotina para escrever uma sequência de valores em um canal, e devolve esse canal a quem a chamou.
Ele também aparece por aí como produtor ou source, dependendo de quem está contando.
Vamos começar pela versão mais direta. A gorrotina gera uma sequência de números inteiros e manda cada um para o canal:
package main
import "fmt"
func sequenciaNumeros(inicial, final int) <-chan int {
saida := make(chan int)
go func() {
for i := inicial; i <= final; i++ {
saida <- i
}
// após gerar todos os valores, fecha o canal
close(saida)
}()
return saida
}
func main() {
valores := sequenciaNumeros(1, 1000)
for valor := range valores {
fmt.Printf("valor: %v\n", valor)
}
}
$ go run .
valor: 1
valor: 2
valor: 3
...
valor: 1000
Nada de surpreendente na saída, mas repare no que não aconteceu: a sequenciaNumeros devolveu o canal na hora, sem esperar número nenhum ficar pronto.
Se ela devolvesse uma fatia (slice) com os mil números, a main não imprimiria o primeiro antes do milésimo estar lá dentro. Com o canal, a gorrotina escreve o 2 enquanto a main imprime o 1, e existe um valor em trânsito por vez em vez de mil na memória.
💡 Antes do Go 1.23 não existia
iter.Seq, e como gorrotina é barata, era assim que se escrevia um iterador em Go. Hoje, para só percorrer valores, oiter.Seqresolve sem gorrotina nenhuma.
O range e o close Link para o cabeçalho
A main lê o canal e imprime os valores. Com o range, a iteração continua até o canal ser fechado.
É por isso que o close(saida) no fim da gorrotina não é enfeite. Sem ele, a main termina de receber o milésimo valor e fica esperando o próximo, que não vem:
fatal error: all goroutines are asleep - deadlock!
E Pam! A gorrotina já terminou, não sobra ninguém para escrever no canal, e o Go derruba o programa.
Repare também no retorno <-chan int, um canal só de leitura: quem chamou não consegue escrever nem fechar o canal por engano.
E se eu parar de ler no meio? Link para o cabeçalho
Lembra da pulga atrás da orelha do post anterior? Voltou.
for valor := range sequenciaNumeros(1, 1000) {
if valor == 3 {
break
}
fmt.Printf("valor: %v\n", valor)
}
O break sai do laço, a main segue a vida e a gorrotina fica parada em saida <- 4, esperando alguém que nunca mais vai ler.
Em um programa que roda e termina ninguém percebe. Mas em um servidor que fica meses no ar, cada leitura interrompida no meio deixa uma gorrotina para trás, com o canal e tudo o que ela segura.
E como a gorrotina fica sabendo que ninguém mais vai ler?
Quem consome avisa que parou Link para o cabeçalho
Sozinha, não fica. Quem consome precisa avisar, e em Go esse aviso vem através de um context.Context:
-func sequenciaNumeros(inicial, final int) <-chan int {
+func sequenciaNumeros(ctx context.Context, inicial, final int) <-chan int {
saida := make(chan int)
go func() {
+ // fecha o canal ao sair, tanto no fim quanto no cancelamento
+ defer close(saida)
for i := inicial; i <= final; i++ {
- saida <- i
+ select {
+ case saida <- i:
+ case <-ctx.Done():
+ return
+ }
}
- // após gerar todos os valores, fecha o canal
- close(saida)
}()
return saida
}
Repare no select. Cada envio disputa com ctx.Done(), e se quem consome cancelar o contexto, a gorrotina sai do laço em vez de ficar presa no envio. Como agora ela tem duas saídas, o close virou defer.
Parando no meio fica assim:
ctx, cancelar := context.WithCancel(context.Background())
valores := sequenciaNumeros(ctx, 1, 1000)
fmt.Printf("valor: %v\n", <-valores)
cancelar()
// drena até o canal fechar
for range valores {
}
fmt.Println("gerador encerrado")
// valor: 1
// gerador encerrado
O canal só fecha quando a gorrotina sai. Logo, se gerador encerrado aparece na tela, ninguém ficou para trás.
Porém a responsabilidade passou para quem consome, que precisa cancelar ao sair mesmo quando leu tudo. Esquecer é tão comum que o go vet tem uma verificação só para isso, a lostcancel.
De onde veio o exemplo Link para o cabeçalho
O código completo está na pasta geradores do concorrencia-go, com um teste que faz esse caminho de cancelar e drenar.
No próximo vem o outro lado do balcão, o trabalhador, que é quem consome desse canal.
Então é isso, pessoal!
Até a próxima!
{}’s