Go

Which of these deadlock, and why does Go detect some deadlocks but not others?

Question 499MediumGo 1.22 to 1.25
// 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

All 38 Tricky Output & Code-Review Puzzles questions