sync.Mutex不可重入——同一goroutine重复Lock会死锁;需可重入时应优先重构而非自实现,若必须则用sync.RWMutex+goroutine标识+计数,但生产环境推荐成熟第三方库。

可重入锁的核心判断:Go 标准库 sync.Mutex 本身不支持可重入
直接说结论:sync.Mutex 是不可重入的——同一个 goroutine 连续调用两次 Lock() 会死锁。这不是 bug,是设计使然。Go 的哲学是“显式优于隐式”,重入逻辑容易掩盖递归调用或资源管理疏漏,所以标准库没提供。如果你确实需要可重入语义(比如递归调用、AOP 拦截、嵌套资源获取),就得自己封装。
用 sync.RWMutex + goroutine ID 实现基础可重入锁
关键点不是“加锁次数”,而是“谁在持锁”。Go 没有公开的 goroutine ID,但可用 unsafe + runtime 粗略提取(仅限调试/非生产场景),更稳妥的做法是用 map 记录当前持有锁的 uintptr(通过 gopark 相关 runtime 符号间接获取)或直接用 sync.Map 存储 goroutine 标识。但实际工程中,推荐更轻量且安全的方式:
- 用
sync.RWMutex保护一个map[uintptr]int,键为 goroutine 标识,值为重入计数 - goroutine 标识可用
func() uintptr { var buf [1]uintptr; runtime.Callers(0, buf[:]); return buf[0] }截取栈帧地址(不保证唯一但足够区分并发 goroutine) - 更可靠的是借助
runtime.GoID()—— 但该函数未导出;替代方案是用reflect.ValueOf(&struct{}{}).Pointer()生成 per-goroutine 临时标识(需配合defer清理)
不过多数真实场景下,应优先考虑重构:把嵌套加锁拆成单一入口,或用 sync.Once + 显式状态机替代。
为什么不要自己实现带计数的 ReentrantMutex?
表面上看,加个 counter 字段和 owner 字段就能模拟 Java 的 ReentrantLock,但 Go 的调度模型会让这类实现极易出错:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- goroutine 可能在
Lock()后被抢占,此时若 panic 或提前 return,Unlock()没执行,计数就泄漏 - 跨 goroutine 传递锁所有权(如 channel 发送后在另一端 unlock)违反可重入定义,但代码可能误写成这样
-
sync.Mutex底层依赖 futex,而自定义计数逻辑无法复用其唤醒机制,容易造成唤醒丢失或虚假唤醒 - 性能上,每次
Lock()都要查 map、原子增减、条件判断,比原生sync.Mutex慢 3–5 倍(基准测试可见)
真正该用的方案:组合 sync.Mutex + 显式重入标记
如果业务逻辑确实绕不开重入(例如 ORM 的 save 方法内部调用自身 hooks),推荐以下模式:
type Resource struct {
mu sync.Mutex
locked bool
locker uintptr // 记录最后一次成功 Lock 的 goroutine 标识
}
func (r *Resource) Lock() {
id := getGID() // 自定义获取 goroutine ID 的函数
r.mu.Lock()
if r.locker == id {
r.mu.Unlock()
return // 已持有,允许重入
}
for r.locked {
r.mu.Unlock()
runtime.Gosched()
r.mu.Lock()
}
r.locked = true
r.locker = id
}
func (r *Resource) Unlock() {
r.mu.Lock()
if r.locker == getGID() {
r.locked = false
r.locker = 0
}
r.mu.Unlock()
}
注意:这个实现仍不完美——它没处理 panic 场景下的自动释放,也没做递归深度限制。生产环境建议用 go.uber.org/ratelimit 或 github.com/cenkalti/rpc2 中经过压测的重入控制模块,而不是手撸。
最常被忽略的一点:可重入锁往往暴露的是设计缺陷。先确认是否真的不能用 channel、context 或状态转移代替锁嵌套——那才是 Go 式解法。










