Which of these deadlock, and why does Go detect some deadlocks but not others?
// A
ch := make(chan int)
ch <- 1 // no receiver can ever run
fmt.Println(<-ch)
// B
ch2 := make(chan int)
go func() {
for i := range 3 {
ch2 <- i
}
// forgot close(ch2)
}()
for v := range ch2 {
fmt.Println(v) // prints 0 1 2, then waits forever
}
// C
var mu sync.Mutex
mu.Lock()
mu.Lock() // Go mutexes are not reentrant
All three deadlock. As the whole program, each one ends with fatal error: all goroutines are asleep - deadlock!.
A: a send on an unbuffered channel blocks until a receiver is ready. The receive is on the next line of the same goroutine, so it never runs. A buffer of 1, or sending in a goroutine, fixes it. B: range ends only on close. C: locking a sync.Mutex you already hold blocks forever, so recursive locking needs restructuring, usually into an internal lockedFoo() helper.
The runtime detector fires only when every goroutine is blocked. In a real server, other goroutines (HTTP listeners, timers, signal handlers) are still alive, so a partial deadlock just hangs requests and leaks goroutines. Detect those with pprof goroutine dumps, SIGQUIT stack traces, or goleak in tests.
More on Tricky Output & Code-Review Puzzles
- Q497What does this select print, and where does the break go?
- Q498What does each channel operation do on nil, open, and closed channels?
- Q500Is msg guaranteed to print "hello"? Compare unbuffered and buffered channels.
- Q501Code review: why does this timeout wrapper leak goroutines?
- Q502Which of these recover calls actually stop the panic?
- Q503Does the recover in main protect this program?