deadlock — all goroutines are asleep em Go
O erro “fatal error: all goroutines are asleep - deadlock!” é um panic do runtime do Go que acontece quando todas as goroutines do programa estão bloqueadas esperando por algo que nunca vai acontecer. O runtime detecta que nenhuma goroutine pode prosseguir e encerra o programa.
Este é um dos erros mais comuns ao trabalhar com concorrência em Go, especialmente com channels e mutexes.
A Mensagem de Erro
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/app/main.go:8 +0x34
exit status 2
Causas Comuns
1. Channel Sem Buffer — Leitura sem Goroutine para Escrever
Um channel sem buffer bloqueia até que haja alguém do outro lado:
package main
func main() {
ch := make(chan int) // Channel sem buffer
// DEADLOCK: main espera para sempre — ninguém envia
valor := <-ch
_ = valor
}
2. Channel Sem Buffer — Escrita sem Goroutine para Ler
O inverso também causa deadlock:
package main
func main() {
ch := make(chan int)
// DEADLOCK: main bloqueia tentando enviar
// (ninguém está lendo do outro lado)
ch <- 42
}
3. WaitGroup Mal Configurado
Esquecer de chamar Done() ou chamar Add() incorretamente:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2) // Espera 2 Done()
go func() {
defer wg.Done()
fmt.Println("goroutine 1")
}()
// Esqueceu a segunda goroutine!
// Só 1 Done() será chamado, mas Add(2) espera 2
// DEADLOCK: wg.Wait() espera para sempre
wg.Wait()
}
4. Mutex Reentrante (Recursive Lock)
Go não suporta mutex reentrante. Tentar bloquear um mutex já bloqueado pela mesma goroutine causa deadlock:
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
func funcA() {
mu.Lock()
defer mu.Unlock()
fmt.Println("funcA")
funcB() // Chama funcB que também tenta bloquear mu
}
func funcB() {
mu.Lock() // DEADLOCK: mu já está bloqueado por funcA na mesma goroutine
defer mu.Unlock()
fmt.Println("funcB")
}
func main() {
funcA()
}
5. Channels Cruzados
Duas goroutines esperando uma pela outra:
package main
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
valor := <-ch1 // Espera ch1...
ch2 <- valor // ...depois envia em ch2
}()
go func() {
valor := <-ch2 // Espera ch2...
ch1 <- valor // ...depois envia em ch1
}()
// DEADLOCK: goroutine 1 espera ch1, goroutine 2 espera ch2
// Ninguém envia primeiro!
select {}
}
Como Resolver
Solução 1: Usar Goroutine para Operações de Channel
Sempre tenha um produtor e consumidor em goroutines separadas:
package main
import "fmt"
func main() {
ch := make(chan int)
// Goroutine produtora
go func() {
ch <- 42
}()
// Main consome
valor := <-ch
fmt.Println(valor) // 42
}
Solução 2: Channels com Buffer
Channels com buffer não bloqueiam enquanto houver espaço:
package main
import "fmt"
func main() {
ch := make(chan int, 1) // Buffer de 1
ch <- 42 // Não bloqueia (buffer tem espaço)
valor := <-ch // Lê do buffer
fmt.Println(valor) // 42
}
Solução 3: Fechar Channels Quando Terminar
Use close() para sinalizar que não há mais dados:
package main
import "fmt"
func gerarNumeros(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // Sinaliza fim
}
func main() {
ch := make(chan int)
go gerarNumeros(ch)
// range termina quando o channel é fechado
for num := range ch {
fmt.Println(num)
}
}
Solução 4: Corrigir WaitGroup
Garanta que Add e Done estejam balanceados:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
tarefas := []string{"A", "B", "C"}
for _, tarefa := range tarefas {
wg.Add(1)
go func(t string) {
defer wg.Done()
fmt.Println("Processando:", t)
}(tarefa)
}
wg.Wait() // Funciona: cada goroutine chama Done()
fmt.Println("Todas as tarefas concluídas")
}
Solução 5: Usar select com Timeout
Evite bloqueios infinitos com select e time.After:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
time.Sleep(5 * time.Second) // Simula operação lenta
ch <- 42
}()
select {
case valor := <-ch:
fmt.Println("Recebido:", valor)
case <-time.After(2 * time.Second):
fmt.Println("Timeout: operação demorou demais")
}
}
Para timeouts mais sofisticados, use o pacote context.
Quando o Runtime NÃO Detecta Deadlock
O runtime do Go só detecta deadlock quando todas as goroutines estão bloqueadas. Se uma goroutine continua executando (mesmo que não contribua), o deadlock parcial passa despercebido:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
// Esta goroutine nunca envia em ch
go func() {
for {
time.Sleep(time.Second)
fmt.Println("estou viva!")
}
}()
// Bloqueia para sempre, mas o runtime NÃO detecta deadlock
// porque a outra goroutine está ativa
<-ch
}
Para detectar esses casos, use profiling e monitore goroutines bloqueadas com pprof.
Dicas para Evitar Deadlocks
Sempre feche channels — quando o produtor terminar, use
close(ch). Leia sobre padrões de concorrência.Use
selectcom default ou timeout — nunca bloqueie indefinidamente sem escape.Evite mutex reentrante — reestruture o código para que a mesma goroutine não tente bloquear o mesmo mutex duas vezes.
Use o race detector —
go run -race .detecta condições de corrida que podem levar a deadlocks. Veja data race detected.Prefira channels a mutexes — channels comunicam intenção mais claramente. Consulte concorrência em Go.
Teste com
-counte-race— testes de concorrência devem rodar múltiplas vezes para expor race conditions:go test -race -count=100.
Para comparação, Rust previne deadlocks e data races em tempo de compilação através do sistema de ownership e borrow checker — uma abordagem fundamentalmente diferente da detecção em runtime do Go.
Erros Relacionados
- race condition / data race — acesso concorrente sem sincronização
- nil pointer dereference — outro panic comum em runtime
- Concorrência em Go — guia completo de goroutines e channels
- Padrões de Concorrência — patterns como fan-out, fan-in, pipeline
- Testes em Go — como testar código concorrente