Concorrência em Go
Uma das maiores forças do Go é seu modelo de concorrência, baseado em goroutines e channels. Diferente de threads tradicionais, goroutines são leves e gerenciadas pelo runtime do Go.
Goroutines
Uma goroutine é uma função executando concorrentemente com outras goroutines. Para criar uma goroutine, use a palavra-chave go:
package main
import (
"fmt"
"time"
)
func dizer(msg string) {
for i := 0; i < 3; i++ {
fmt.Println(msg)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go dizer("goroutine") // executa concorrentemente
dizer("main") // executa na goroutine principal
}
Características das Goroutines
- Leves: ~2KB de stack (vs ~1MB para threads OS)
- Multiplexadas: milhares podem rodar em poucos threads OS
- Gerenciadas pelo runtime: scheduling automático
// Criar 1000 goroutines é trivial
for i := 0; i < 1000; i++ {
go func(n int) {
fmt.Println(n)
}(i)
}
Channels
Channels são a forma idiomática de comunicação entre goroutines. Seguem o princípio:
“Não comunique compartilhando memória; compartilhe memória comunicando.”
Criando e Usando Channels
// Criar channel
ch := make(chan int)
// Enviar valor
go func() {
ch <- 42 // envia 42 para o channel
}()
// Receber valor
valor := <-ch // recebe do channel
fmt.Println(valor) // 42
Channels Bufferizados
// Channel com buffer de tamanho 3
ch := make(chan int, 3)
ch <- 1 // não bloqueia
ch <- 2 // não bloqueia
ch <- 3 // não bloqueia
ch <- 4 // BLOQUEIA (buffer cheio)
Direção de Channels
// Apenas envio
func produtor(ch chan<- int) {
ch <- 1
}
// Apenas recebimento
func consumidor(ch <-chan int) {
valor := <-ch
}
Fechando Channels
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // sinaliza que não há mais valores
}()
// Range itera até o channel fechar
for v := range ch {
fmt.Println(v)
}
Verificando se Channel está Fechado
valor, ok := <-ch
if !ok {
fmt.Println("channel fechado")
}
Select
O select permite esperar em múltiplos channels:
select {
case msg1 := <-ch1:
fmt.Println("recebeu de ch1:", msg1)
case msg2 := <-ch2:
fmt.Println("recebeu de ch2:", msg2)
case ch3 <- valor:
fmt.Println("enviou para ch3")
default:
fmt.Println("nenhum channel pronto")
}
Timeout com Select
select {
case res := <-ch:
fmt.Println(res)
case <-time.After(1 * time.Second):
fmt.Println("timeout!")
}
Padrões Comuns
Worker Pool
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("worker %d processando job %d\n", id, j)
time.Sleep(time.Second)
results <- j * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
// Criar 3 workers
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// Enviar 5 jobs
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
// Coletar resultados
for a := 1; a <= 5; a++ {
<-results
}
}
Fan-out / Fan-in
// Fan-out: uma fonte, múltiplos consumidores
func fanOut(input <-chan int, workers int) []<-chan int {
outputs := make([]<-chan int, workers)
for i := 0; i < workers; i++ {
outputs[i] = processador(input)
}
return outputs
}
// Fan-in: múltiplas fontes, um consumidor
func fanIn(inputs ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
for _, in := range inputs {
wg.Add(1)
go func(ch <-chan int) {
defer wg.Done()
for v := range ch {
out <- v
}
}(in)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
Pipeline
func gerador(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func quadrado(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
// Pipeline: gerador -> quadrado
for v := range quadrado(gerador(1, 2, 3, 4)) {
fmt.Println(v) // 1, 4, 9, 16
}
}
sync.WaitGroup
Para esperar múltiplas goroutines terminarem:
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
fmt.Println(n)
}(i)
}
wg.Wait() // espera todas terminarem
Armadilhas Comuns
1. Race Condition
// ERRADO - race condition
counter := 0
for i := 0; i < 1000; i++ {
go func() {
counter++ // múltiplas goroutines acessando
}()
}
// CORRETO - usando mutex
var mu sync.Mutex
counter := 0
for i := 0; i < 1000; i++ {
go func() {
mu.Lock()
counter++
mu.Unlock()
}()
}
2. Goroutine Leak
// ERRADO - goroutine nunca termina
func leak() {
ch := make(chan int)
go func() {
val := <-ch // bloqueia para sempre
fmt.Println(val)
}()
// ch nunca recebe valor, goroutine fica pendurada
}
// CORRETO - usar contexto para cancelamento
func semLeak(ctx context.Context) {
ch := make(chan int)
go func() {
select {
case val := <-ch:
fmt.Println(val)
case <-ctx.Done():
return // sai quando contexto cancela
}
}()
}
3. Fechar Channel do Lado Errado
// REGRA: quem ENVIA fecha o channel
// Nunca feche um channel do lado receptor
Detectando Race Conditions
# Build com detector de race
go build -race ./...
# Testar com detector de race
go test -race ./...
Recursos
Última atualização: Janeiro 2026
Veja também
- WebSocket com Go — Use concorrência num chat real-time
- Microservices com Go — Concorrência em microservices
- Tratamento de Erros — Erros em código concorrente
- Go Cheatsheet — Referência rápida de goroutines e channels
- Vagas Go — Empresas que buscam expertise em concorrência