Go

What happens here, and when does the runtime NOT detect a deadlock?

Question 251MediumGo 1.22 to 1.25
func main() {
	ch := make(chan int)
	ch <- 1 // no receiver
	fmt.Println(<-ch)
}

It prints fatal error: all goroutines are asleep - deadlock!. The send on an unbuffered channel blocks until a receiver is ready, and the only goroutine that could receive is the one that is blocked. With make(chan int, 1) the program prints 1.

The runtime detects a deadlock only when every goroutine is blocked. Real deadlocks go undetected in these cases:

  • Any other goroutine is still runnable or sleeping: an HTTP server, a time.Ticker loop, a signal handler.
  • Goroutines are blocked in syscalls or cgo.
  • Only part of the program deadlocks, for example two goroutines holding locks in opposite orders (AB/BA).

In production these show up as hung requests and goroutine counts that keep climbing.

Diagnosis tools:

  • kill -QUIT (SIGQUIT) dumps all goroutine stacks.
  • /debug/pprof/goroutine?debug=2 shows goroutines stuck in sync.Mutex.Lock or chan send, along with how long they have been waiting.
  • The mutex and block profiles show contention.

Prevention:

  • Acquire locks in a consistent order.
  • Never hold a lock while sending on a channel or calling unknown code such as callbacks.
  • Use timeouts or contexts on blocking operations.

More on Concurrency Patterns & sync

All 38 Concurrency Patterns & sync questions