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.Tickerloop, 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=2shows goroutines stuck insync.Mutex.Lockorchan 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
- Q249Which newer context features matter for concurrent code: WithCancelCause, AfterFunc, WithoutCancel?
- Q250Is time.After in a select loop a leak? What changed in Go 1.23 timers?
- Q252Design a thread-safe loading cache. Why is naive double-checked locking with a plain flag wrong in Go?
- Q253Channels or mutexes: how do you decide? ("Share memory by communicating")
- Q254How do you test concurrent and time-dependent code deterministically? (testing/synctest)
- Q255What does this print? Can recover in main catch a panic from another goroutine?