别再乱用 sync.Once 了!这几个细节能让你避免死锁
在 Go 中,sync.Once 是保证某个函数只执行一次的利器,常用于单例模式、懒加载等场景。但用不对,它可能成为你代码中的“隐形炸弹”。
典型错误:在 Do 方法中嵌套调用 Do
var (
once sync.Once
data string
)
func initData() {
once.Do(func() {
// 假设这里又调用了 once.Do
once.Do(func() {
data = "initialized"
})
})
}
这会直接死锁! 因为 sync.Once 内部通过 mutex 和 atomic 控制状态,第一次 Do 未完成时,第二次 Do 会阻塞等待,形成死锁。
另一个陷阱:在 Do 中 panic
如果 once.Do 传入的函数 panic,sync.Once 会认为该次执行“已完成”。下次再调用 Do 时,函数将不再执行。
once.Do(func() {
panic("something wrong")
})
// 后续调用 once.Do 不会重新执行
这意味着你的初始化逻辑可能永久失效。
最佳实践
- 避免嵌套:不要在
once.Do的回调函数内对同一个once再次调用Do。 - 处理 panic:若
Do内的代码可能 panic,务必用recover捕获,并考虑重置sync.Once(虽然官方不提供重置方法,但可通过替换once变量实现)。 - 简单至上:
sync.Once适合简单的初始化,复杂逻辑建议使用init()函数或显式的sync.Mutex控制。
用好 sync.Once,让你的 Go 并发代码更稳健!