在Go语言中不使用channel是否可能发生死锁?
Absolutely! Channels are just one common synchronization mechanism that can lead to deadlocks in Go, but deadlocks boil down to circular waits and unresolvable resource contention—you can hit them with other sync primitives, or even with plain goroutines waiting on each other without any explicit tools.
Let’s walk through a few concrete examples:
1. Deadlock with nested mutex locks
This is the classic "circular wait" scenario using sync.Mutex: two goroutines each hold one lock, then wait for the other lock that the other goroutine is holding. Neither can proceed.
package main import ( "sync" ) func main() { var mutexA, mutexB sync.Mutex go func() { mutexA.Lock() defer mutexA.Unlock() // Give the other goroutine time to grab mutexB first mutexB.Lock() defer mutexB.Unlock() println("Goroutine 1 finished") }() go func() { mutexB.Lock() defer mutexB.Unlock() // Give the first goroutine time to grab mutexA first mutexA.Lock() defer mutexA.Unlock() println("Goroutine 2 finished") }() // Block the main goroutine to prevent program exit select {} }
When you run this, both goroutines will hang forever—each holds one lock and waits for the other, creating an unbreakable cycle.
2. Deadlock with misused sync.WaitGroup
Another common case is misusing sync.WaitGroup where goroutines end up waiting on the same group that depends on them to complete:
package main import "sync" func main() { var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() // This goroutine waits for the WaitGroup to complete... wg.Wait() println("Goroutine 1 done") }() go func() { defer wg.Done() // ...and so does this one wg.Wait() println("Goroutine 2 done") }() // The main goroutine also waits for the WaitGroup wg.Wait() }
Here, the main goroutine waits for the two child goroutines to call Done(), but each child goroutine is waiting for the WaitGroup to be marked as complete. None of them can make progress—deadlock.
3. Plain goroutines in a circular wait
Even without any sync libraries, you can create a deadlock if goroutines are stuck waiting for each other to trigger a state change:
package main var ready1, ready2 bool func main() { go func() { // Wait for goroutine 2 to signal it's ready for !ready2 { // Spin-wait (no progress made) } ready1 = true println("Goroutine 1 finished") }() go func() { // Wait for goroutine 1 to signal it's ready for !ready1 { // Spin-wait (no progress made) } ready2 = true println("Goroutine 2 finished") }() // Keep main goroutine alive select {} }
Both goroutines are stuck in infinite loops waiting for the other to set a flag. Neither will ever proceed, resulting in a deadlock (or a form of livelock, but from the program's perspective, it's effectively stuck forever).
The core of deadlocks
Remember, deadlocks happen when four conditions are met:
- Mutual exclusion: Resources can't be shared (e.g., a mutex lock is held by only one goroutine at a time)
- Hold and wait: A goroutine holds a resource while waiting for another
- No preemption: Resources can't be taken away from a goroutine forcefully
- Circular wait: A cycle of goroutines each waiting for a resource held by the next in the cycle
Channels are just one way to create these conditions, but they’re far from the only way. Any code that creates this circular dependency will deadlock, regardless of whether channels are involved.
内容的提问来源于stack exchange,提问作者Rza

