sync.once仅保证do内函数执行一次,不管理对象生命周期、不保护变量读写安全、不可重置,需配合锁使用且不能用于热更新场景。

sync.Once 不能用来做“单例对象初始化”的通用方案,它只保证函数体执行一次,不负责对象生命周期管理。
sync.Once.Do() 的参数必须是 func()
它只接受一个无参无返回值的函数类型 func()。传入带参函数、闭包捕获变量不加防护、或试图返回值都会编译失败。
- 错误写法:
once.Do(func(x int) { ... })—— 参数不匹配 - 危险写法:
once.Do(func() { value = expensiveInit(param) })——param可能在多次调用间变化,但Do不检查闭包外变量 - 正确姿势:所有依赖提前计算好,封装成纯
func(),例如once.Do(initFunc)或once.Do(func() { value = expensiveInit() })
并发场景下 sync.Once 不保护变量读写安全
sync.Once 只确保 Do 内部函数最多执行一次,但它不提供对被初始化变量本身的读写同步。如果多个 goroutine 在 Do 返回后同时读/写该变量,仍需额外同步机制。
- 典型误用:初始化一个
map后直接并发读写 ——sync.Once不等于sync.Map - 正确组合:
sync.Once+sync.RWMutex(读多写少)或sync.Mutex(写频繁) - 性能注意:不要把整个访问逻辑塞进
Do,否则每次读都得抢锁;初始化和访问要拆开
sync.Once 无法重置或重复使用
一旦 Do 执行过,内部状态就永久标记为 “done”,没有公开 API 能清空或复用。测试中反复初始化会失效,mock 也难绕过。
- 单元测试陷阱:
once.Do(...)在 test helper 里调用一次,后续测试用例全跳过 - 替代思路:测试时改用局部
sync.Once变量,或用函数选项模式把初始化逻辑外移 - 生产环境注意:它不适合配置热更新、连接池重建等需要“再次初始化”的场景
真正容易被忽略的是:sync.Once 的“一次”只针对它的 Do 方法调用本身,不是针对某个变量、某个业务含义的“首次”。别把它当成线程安全的懒加载银弹,尤其当初始化逻辑和后续使用逻辑边界模糊时。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











