Go中defer recover是否会阻止mutex Unlock?及死锁问题排查
问题描述
我编写了如下简化代码,希望实现Manager的TryHandle函数仅在mutex未锁定时执行,否则跳过,该函数可被多个goroutine访问:
var ( mu sync.Mutex ) // can be accessed by multiple go routines func (m *Manager) TryHandle(work *Work) { if mu.TryLock() { defer mu.Unlock() defer func() { if r := recover(); r != nil { fmt.Printf("Recovered from panic. Error: %s\n", r) fmt.Println("stacktrace from panic: \n" + string(debug.Stack())) } }() m.HandleWork(work) } else { fmt.Println("mutex is busy.") } }
程序起初运行正常,TryHandle每秒被调用5-10次,仅极少数情况提示mutex繁忙。但今早发现每次调用TryHandle都输出"mutex is busy",且有一个CPU占用100%。
最明显的原因可能是某次m.HandleWork()未返回导致mutex未解锁,但日志不足无法证实。我几乎确定m.HandleWork无问题,想请教:是否可能因panic导致defer recover阻止了defer mu.Unlock()?或是将mu定义为全局变量而非Manager结构体字段的问题?
问题解答
panic不会阻止
mu.Unlock()执行:Go里defer是按后进先出顺序执行的,你代码中recover的defer后声明,会先执行,执行完后mu.Unlock()的defer依然会正常运行。哪怕m.HandleWork()触发panic,锁也能正常释放,不会出现锁永久占用的情况。全局mutex存在潜在问题:如果程序中有多个
Manager实例,全局mutex会让所有实例的TryHandle共用同一把锁,锁竞争会更激烈,但这不会直接导致锁永久卡住。结合你CPU100%的现象,更大可能是持有锁的goroutine在m.HandleWork()里陷入了无限循环,既不退出也不释放锁,同时把CPU占满。额外排查建议:
- 检查
m.HandleWork()的逻辑,看是否存在循环条件永远无法满足退出的情况; - 用pprof工具分析CPU占用最高的goroutine调用栈,直接定位到卡死的代码位置;
- 临时在
mu.TryLock()成功后打印当前goroutine ID,问题复现时就能知道是哪个goroutine持有锁,再针对性排查。
- 检查
内容的提问来源于stack exchange,提问作者flo
相关产品推荐
相关产品推荐

