Channels
Esta aula aborda o conceito de channels em Go, explicando como criar, enviar e receber dados através de canais, incluindo canais com buffer e direção de canais. São apresentados exemplos práticos e exercícios para fixação.
Channels são um mecanismo de comunicação entre goroutines em Go. Eles permitem que uma goroutine envie dados para outra, garantindo sincronização e segurança em concorrência. Nesta aula, veremos como criar, usar e controlar channels, incluindo canais com buffer e direção de canais.
Channels são tipados e podem ser unidirecionais ou bidirecionais. Eles são fundamentais para o modelo de concorrência do Go, evitando condições de corrida e simplificando a lógica de programas concorrentes.
Criação
Um channel é criado com a função make, especificando o tipo de dado que será transportado. A sintaxe é make(chan Tipo) para canais sem buffer e make(chan Tipo, capacidade) para canais com buffer.
Exemplo:
ch := make(chan int) // canal sem buffer
chBuf := make(chan string, 10) // canal com buffer de capacidade 10Channels são referências; quando passados para funções, compartilham o mesmo canal. O zero de um channel é nil, e enviar ou receber de um canal nil bloqueia para sempre.
send e receive
Para enviar um valor para um channel, usa-se a sintaxe ch <- valor. Para receber, usa-se valor := <-ch. O envio e recebimento são operações bloqueantes em canais sem buffer: a goroutine que envia espera até que outra goroutine receba, e vice-versa.
Exemplo:
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "Olá, mundo!"
}()
msg := <-ch
fmt.Println(msg)
}Neste exemplo, a goroutine anônima envia uma string para o canal, e a main recebe. A sincronização ocorre naturalmente.
Channels com buffer
Channels com buffer têm uma capacidade definida. O envio só bloqueia quando o buffer está cheio, e o recebimento só bloqueia quando o buffer está vazio. Isso permite que o produtor envie vários itens sem esperar imediatamente pelo consumidor.
Exemplo:
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
// ch <- 3 // isso bloquearia, pois buffer está cheio
fmt.Println(<-ch)
fmt.Println(<-ch)
}Neste código, o buffer de capacidade 2 permite enviar dois valores sem bloqueio. O terceiro envio causaria deadlock se não houver consumidor.
Direção de channel
Channels podem ser declarados como unidirecionais: apenas para envio (chan<- Tipo) ou apenas para recebimento (<-chan Tipo). Isso é útil para restringir o uso em funções, aumentando a segurança.
Exemplo:
package main
import "fmt"
func produtor(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}
func consumidor(ch <-chan int) {
for valor := range ch {
fmt.Println(valor)
}
}
func main() {
ch := make(chan int, 5)
go produtor(ch)
consumidor(ch)
}A função produtor só pode enviar (chan<- int), enquanto consumidor só pode receber (<-chan int). Isso evita usos incorretos.
Boas práticas
Sempre feche um canal quando não houver mais envios, para que o receptor saiba que terminou. Use range para iterar sobre um canal até que ele seja fechado. Evite enviar em canais fechados (causa pânico). Prefira canais com buffer quando a taxa de produção e consumo for diferente.
Referências
- A Tour of Go: Channels
- Effective Go: Channels
- Go Specification: Channel types
- Package builtin: make
- Go by Example: Channels
Exercícios
Crie um canal sem buffer de inteiros. Em uma goroutine, envie o número 42. Na main, receba e imprima.
✓ Resposta:package main import "fmt" func main() { ch := make(chan int) go func() { ch <- 42 }() fmt.Println(<-ch) }Crie um canal com buffer de capacidade 3. Envie três strings sem usar goroutines e depois receba todas.
✓ Resposta:package main import "fmt" func main() { ch := make(chan string, 3) ch <- "a" ch <- "b" ch <- "c" fmt.Println(<-ch, <-ch, <-ch) }Escreva uma função que recebe um canal de envio de inteiros e envia os números de 1 a 5. Na main, crie um canal com buffer 5, chame a função em uma goroutine e receba os valores usando range.
✓ Resposta:package main import "fmt" func produtor(ch chan<- int) { for i := 1; i <= 5; i++ { ch <- i } close(ch) } func main() { ch := make(chan int, 5) go produtor(ch) for v := range ch { fmt.Println(v) } }Crie uma função que recebe um canal de recebimento de strings e imprime cada string recebida. Na main, crie um canal, envie duas strings em goroutines separadas e chame a função.
✓ Resposta:package main import "fmt" func consumidor(ch <-chan string) { for s := range ch { fmt.Println(s) } } func main() { ch := make(chan string, 2) go func() { ch <- "hello" }() go func() { ch <- "world" }() go consumidor(ch) // Aguarda um pouco para ver a saída fmt.Scanln() }Explique o que acontece se você enviar em um canal fechado. Escreva um pequeno programa que demonstre isso (pode usar recover).
✓ Resposta:Enviar em um canal fechado causa pânico (panic). Exemplo:package main func main() { ch := make(chan int) close(ch) ch <- 1 // panic: send on closed channel }