为何我的Go互斥锁代码中的goroutine发生死锁?
问题:每个Key独立锁的Map出现死锁
问题场景
尝试实现一个为每个Key配备独立锁的Map:创建特定Key的锁时用全局互斥锁保护Map的写入操作,锁创建完成后使用该Key对应的锁执行任务,任务结束后释放锁。但测试单个Key的并发修改时出现死锁。
原代码
package main import ( "fmt" "sync" "time" ) var count int var globalMutex *sync.RWMutex var mutexes map[int]*sync.Mutex func MyLock(index int) { fmt.Println("Aquiring Lock") globalMutex.Lock() defer globalMutex.Unlock() count++ if mutexes == nil { mutexes = make(map[int]*sync.Mutex) } if _, ok := mutexes[index]; !ok { mutexes[index] = &sync.Mutex{} } fmt.Println("Aquiring 2nd Lock") mutexes[index].Lock() fmt.Println("Aquired Lock") } func MyUnlock(index int) { globalMutex.Lock() defer globalMutex.Unlock() mutexes[index].Unlock() } func main() { var wg sync.WaitGroup globalMutex = &sync.RWMutex{} wg.Add(500) for i := 0; i < 500; i++ { go func(i int) { defer wg.Done() MyLock(2) time.Sleep(1 * time.Second) fmt.Println(i) MyUnlock(2) }(i) } wg.Wait() fmt.Println(mutexes) fmt.Println(count) }
死锁原因
- 锁持有逻辑错误:
MyLock中先获取globalMutex写锁,随后获取Key对应的锁,但globalMutex的解锁被延迟到MyLock函数结束后执行。这会导致后续goroutine进入MyLock时:- 成功获取
globalMutex(第一个goroutine的MyLock执行完毕释放后) - 尝试获取Key锁时被阻塞,同时持续持有
globalMutex
- 成功获取
- 双向等待形成死锁:第一个goroutine执行完任务后调用
MyUnlock,需要获取globalMutex才能解锁Key锁,但此时globalMutex被阻塞在Key锁等待的goroutine持有,最终双方互相等待:- 第一个goroutine:持有Key锁 → 等待
globalMutex - 阻塞的goroutine:持有
globalMutex→ 等待Key锁
- 第一个goroutine:持有Key锁 → 等待
修复方案
- 提前释放全局锁:
globalMutex仅需保护mutexesMap的读写操作,确认Key锁存在并获取后,立即释放globalMutex,无需持有至函数结束 - 解锁无需全局锁:Key锁一旦创建后不会被删除(当前逻辑无删除操作),因此
MyUnlock无需获取globalMutex,直接解锁Key锁即可(若后续有删除锁的逻辑,可加全局读锁确保读取安全)
修复后的代码
package main import ( "fmt" "sync" "time" ) var count int var globalMutex *sync.RWMutex var mutexes map[int]*sync.Mutex func MyLock(index int) { fmt.Println("Aquiring global lock") globalMutex.Lock() count++ if mutexes == nil { mutexes = make(map[int]*sync.Mutex) } // 确保当前Key的锁存在 if _, ok := mutexes[index]; !ok { mutexes[index] = &sync.Mutex{} } // 先拿到Key锁的引用,再释放全局锁 mutex := mutexes[index] globalMutex.Unlock() fmt.Println("Aquiring key lock") mutex.Lock() fmt.Println("Aquired key lock") } func MyUnlock(index int) { // 加全局读锁确保读取锁引用时的安全 globalMutex.RLock() mutex := mutexes[index] globalMutex.RUnlock() mutex.Unlock() fmt.Println("Released key lock") } func main() { var wg sync.WaitGroup globalMutex = &sync.RWMutex{} mutexes = make(map[int]*sync.Mutex) // 提前初始化简化逻辑 wg.Add(500) for i := 0; i < 500; i++ { go func(i int) { defer wg.Done() MyLock(2) time.Sleep(1 * time.Millisecond) // 缩短sleep加快测试 fmt.Println(i) MyUnlock(2) }(i) } wg.Wait() fmt.Println(mutexes) fmt.Println(count) }
额外优化说明
- 提前初始化
mutexesMap,避免MyLock中的nil判断逻辑 MyUnlock使用全局读锁,确保读取Key锁引用时不会被其他写操作干扰- 缩短
Sleep时间,加快测试验证效率
内容的提问来源于stack exchange,提问作者Critical_Rebel
相关产品推荐
相关产品推荐

