When does the Go runtime report "all goroutines are asleep - deadlock!" and when doesn't it?
Question 169MediumGo 1.22 to 1.25
The scheduler's checkdead function detects a deadlock only when no goroutine can make progress. That means there are no running Ms, and every remaining goroutine is parked with no timers pending and no network waiters that could wake one.
func main() {
ch := make(chan int)
ch <- 1 // fatal error: all goroutines are asleep - deadlock!
}
It does not fire in these cases:
- Some other goroutine is still running or sleeping. A partial deadlock in two workers while an HTTP server runs is silent, and that is a leak.
- A
time.Tickeror pending timer exists, or goroutines are waiting on the netpoller.http.ListenAndServealone keeps the program "alive". - Goroutines are blocked in cgo or syscalls.
select {}in main while a background goroutine is alive.
Also, locking a sync.Mutex you already hold is not re-entrant. In a busy program it just hangs.
What the interviewer wants to hear: do not count on the runtime to find deadlocks. Use lock ordering, timeouts and context, goroutine profiles, and testing/synctest, which reports goroutines blocked inside its bubble.
More on Goroutines & the Scheduler
- Q167What happens if a goroutine panics? Can the parent recover it?
- Q168What does this print? (Closure capture and the Go 1.22 loop variable change)
- Q170Concurrency vs. parallelism in Go — can goroutines run in parallel with GOMAXPROCS=1?
- Q171What are the goroutine states and what transitions happen on a channel block?
- Q172How can you observe the scheduler? (schedtrace, go tool trace, pprof)
- Q173Why can many CPU-bound goroutines hurt performance, and how do you size a worker pool?