A circular wait is a dependency cycle of two or more concurrent executions: each is blocked waiting for a resource held by the next execution in the cycle. It is one of four conditions required for deadlock, alongside mutual exclusion, waiting while holding resources, and no preemption. A cycle can involve any number of executions, not only two. section 11.1.1
Use a resource allocation graph to trace who holds each resource and who is waiting. A closed chain reveals the circular dependency behind a deadlock. section 11.1.1
With channels, a circular wait can arise even without explicit locks. A directory-handling goroutine may be blocked sending file paths on one channel while a file-handling goroutine is blocked sending directory paths on another. Neither goroutine can resume to receive the message that would unblock the other. section 11.3
Directory traversal deadlock
Two unbuffered channels carry directory paths and file paths. The directory handler fills the files channel and waits for a receiver; meanwhile, the file handler finds a subdirectory, tries to send it on the dirs channel, and waits for a receiver. Because both handlers are stopped on sends, traversal deadlocks. Adding a finite buffer or more file-handler goroutines only postpones the failure when a sufficiently large directory is reached. section 11.3 section 11.3
The two-channel diagram makes the blocking cycle visible: each goroutine needs the other to receive before it can continue. section 11.3
Break the cycle rather than merely delaying it:
- Send channel values from a newly spawned goroutine, freeing the current handler to continue receiving. section 11.3
- Use select to coordinate receiving and sending so one handler can make progress on either channel operation. section 11.3
- Redesign message flow to avoid circular dependencies; channel capacity acts like a mutually exclusive resource, so bounded buffering is not a general cure. section 11.5
- When resource needs are known in advance, request resources in a predefined order to prevent deadlocks. section 11.5
A resource allocation graph can support cycle detection. Go’s runtime also detects deadlock, but only when every goroutine is blocked; it then terminates the program with an error. Treat that error as a signal to inspect waiting relationships, not as proof that every partial stall will be reported. section 11.5
Practice: score updates and channel handlers
Concurrent score updates that lock several player mutexes may deadlock. Practice preventing the problem by changing the locking approach, then adapt the file handler to use select for both receiving and sending across the two channels. section 11.4 section 11.4
