You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 03:07:33