自定义Go Mutex遭遇死锁求助:all goroutines are asleep - deadlock
问题描述
尝试实现一个带自定义行为的简单Mutex,但测试时触发死锁错误:all goroutines are asleep - deadlock!。第一个for循环代码块运行正常,但第二个代码块调用m.Unlock()时失败。实现逻辑是用带缓冲的channel,通过发送值实现锁,不清楚为什么会出现goroutine死锁问题。
错误日志
Result: === RUN TestNiceMutex_Lock === RUN TestNiceMutex_Lock/test nicemutex_test.go:36: main: lock fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: testing.(*T).Run(0xc00007b380, {0xcee048?, 0xc1f6cd?}, 0xcf8388) C:/Program Files/Go/src/testing/testing.go:1649 +0x3c8 testing.runTests.func1(0xdecd00?) C:/Program Files/Go/src/testing/testing.go:2054 +0x3e testing.tRunner(0xc00007b380, 0xc0000b7c48) C:/Program Files/Go/src/testing/testing.go:1595 +0xff testing.runTests(0xc00009a140?, {0xde0d90, 0x1, 0x1}, {0xbc3345?, 0xc0000b7d08?, 0x0?}) C:/Program Files/Go/src/testing/testing.go:2052 +0x445 testing.(*M).Run(0xc00009a140) C:/Program Files/Go/src/testing/testing.go:1925 +0x636 main.main() _testmain.go:47 +0x19c goroutine 6 [chan receive]: testing.(*T).Run(0xc00007b520, {0xceae57?, 0xc78420?}, 0xc00023c000) C:/Program Files/Go/src/testing/testing.go:1649 +0x3c8 simply/nicemutex.TestNiceMutex_Lock(0x0?) C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:26 +0xba testing.tRunner(0xc00007b520, 0xcf8388) C:/Program Files/Go/src/testing/testing.go:1595 +0xff created by testing.(*T).Run in goroutine 1 C:/Program Files/Go/src/testing/testing.go:1648 +0x3ad (content is repeated) goroutine 97 [chan send]: simply/nicemutex.lock() C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex.go:6 simply/nicemutex.TestNiceMutex_Lock.func1() C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:12 +0x25 created by simply/nicemutex.TestNiceMutex_Lock in goroutine 6 C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:11 +0x2c goroutine 117 [chan send]: simply/nicemutex.(*NiceMutex).Lock(0xc000250008) C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex.go:25 +0x99 simply/nicemutex.TestNiceMutex_Lock.func2.1() C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:32 +0x5f created by simply/nicemutex.TestNiceMutex_Lock.func2 in goroutine 116 C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:30 +0xa5 Process finished with the exit code 1
测试代码
package nicemutex import ( "sync" "testing" "time" ) func TestNiceMutex_Lock(t *testing.T) { for i := 0; i < 100; i++ { go func() { lock() unlock() }() lock() unlock() } tests := []struct { name string }{ {"test"}, } wg := sync.WaitGroup{} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var m NiceMutex m.Lock() wg.Add(1) go func() { defer m.Unlock() m.Lock() t.Log("go: lock") wg.Done() }() t.Log("main: lock") time.Sleep(time.Second * 1) m.Unlock() // <- 错误发生处 wg.Wait() }) } } package nicemutex var internalLock = make(chan struct{}, 1) func lock() { internalLock <- struct{}{} // <- 错误发生处 } func unlock() { <-internalLock } type NiceMutex struct { ch chan struct{} } func (m *NiceMutex) Lock() { defer unlock() lock() if m.ch == nil { m.ch = make(chan struct{}, 1) } m.ch <- struct{}{} } func (m *NiceMutex) Unlock() { defer unlock() lock() // <- 错误发生处 if m.ch == nil { return } <-m.ch }
更新后的代码(仍存在相同问题)
package nicemutex import ( "log" "time" ) var ( internalLock2 = make(chan struct{}, 1) TraceEnabled2 = false ) type NiceMutex2 struct { ch chan struct{} lockedOn int64 } func (m *NiceMutex2) Lock() { internalLock2 <- struct{}{} defer func() { <-internalLock2 }() m.lockedOn = time.Now().UnixNano() if m.ch == nil { m.ch = make(chan struct{}, 1) } m.ch <- struct{}{} trace2("locked") } func (m *NiceMutex2) LockForSec() { internalLock2 <- struct{}{} defer func() { <-internalLock2 }() on := time.Now().UnixNano() m.lockedOn = on if m.ch == nil { m.ch = make(chan struct{}, 1) } m.ch <- struct{}{} go func(lockedOn int64) { <-time.After(time.Second) internalLock2 <- struct{}{} defer func() { <-internalLock2 }() if m.lockedOn == lockedOn { select { case <-m.ch: trace2("timeout used", true) default: trace2("timeout ignore") } } }(on) trace2("locked") } func (m *NiceMutex2) Unlock() { internalLock2 <- struct{}{} defer func() { <-internalLock2 }() m.lockedOn = time.Now().UnixNano() if m.ch == nil { return } select { case <-m.ch: default: } trace2("unlocked") } func trace2(msg string, ignoreEnabling ...bool) { if TraceEnabled2 || (len(ignoreEnabling) > 0 && ignoreEnabling[0]) { log.Println(msg) } }
问题分析与解决
核心死锁原因
- 全局锁的单点竞争:你用了一个全局的带缓冲channel(
internalLock/internalLock2)作为所有Mutex实例的内部锁,当多个goroutine同时调用Lock/Unlock时,会竞争这个全局资源,一旦缓冲被占满,后续发送操作会阻塞。 - 实例锁的逻辑冲突:以
NiceMutex.Lock为例,主goroutine调用m.Lock()后,m.ch的缓冲已被占满;子goroutine调用m.Lock()时,即使拿到全局锁,也会卡在m.ch <- struct{}{}步骤,且全局锁未释放,导致主goroutine的m.Unlock()无法获取全局锁,最终所有goroutine阻塞触发死锁。 - 更新后的代码未解决根本问题:
internalLock2依然是全局单点竞争,LockForSec中启动的超时goroutine还会额外抢占全局锁,加剧阻塞风险。
修复方案
- 移除全局锁:每个Mutex实例应拥有独立的同步原语,用channel实现Mutex的正确方式是让每个实例持有一个缓冲为1的channel,
Lock时发送值,Unlock时接收值,无需全局锁。 - 修正锁逻辑:确保
Lock/Unlock仅针对当前实例的channel操作,避免跨实例竞争。
修复后的示例代码:
package nicemutex import ( "sync" "testing" "time" ) type NiceMutex struct { ch chan struct{} } // 初始化Mutex,确保channel缓冲为1 func NewNiceMutex() *NiceMutex { return &NiceMutex{ ch: make(chan struct{}, 1), } } func (m *NiceMutex) Lock() { m.ch <- struct{}{} } func (m *NiceMutex) Unlock() { select { case <-m.ch: default: // 避免重复解锁导致阻塞 } } func TestNiceMutex_Lock(t *testing.T) { tests := []struct { name string }{ {"test"}, } wg := sync.WaitGroup{} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := NewNiceMutex() m.Lock() wg.Add(1) go func() { defer m.Unlock() m.Lock() t.Log("go: lock") wg.Done() }() t.Log("main: lock") time.Sleep(time.Second * 1) m.Unlock() wg.Wait() }) } }
额外说明
如果需要实现超时自动释放的锁(如LockForSec),需注意:
- 每个实例的状态变量要用
sync.Mutex保护,避免并发读写冲突 - 超时释放时要校验锁的当前持有者,防止误释放其他goroutine持有的锁
内容的提问来源于stack exchange,提问作者Aleksey Fomin
相关产品推荐
相关产品推荐

