sync.Once 是 Go 中轻量安全的单例初始化方案,但仅保障“执行一次”,不处理错误、生命周期或并发访问;正确用法需配合包级变量、错误返回及幂等初始化逻辑。

sync.Once 是 Go 里最轻量、最安全的单例初始化方案,但它只管“初始化一次”,不负责对象生命周期或并发访问控制——用错地方反而会引入竞态或 panic。
为什么不能直接用 sync.Once 包裹结构体指针赋值
常见错误是写成这样:
var once sync.Once
var instance *MyService
func GetInstance() *MyService {
once.Do(func() {
instance = &MyService{} // ❌ 危险!如果 MyService 初始化失败,instance 可能为 nil
})
return instance // 可能返回 nil,调用方未感知
}
问题在于:sync.Once.Do 不捕获函数 panic,也不支持返回错误;一旦初始化逻辑出错(比如连接数据库失败),instance 保持零值,后续调用直接 panic 或静默失败。
- 必须把初始化逻辑封装成「可重试但幂等」的函数,且内部处理好错误回退
- 推荐在
Do内完成全部初始化并保证instance非 nil,或改用带错误返回的懒加载模式 - 不要在
Do里做阻塞太久的操作(如长耗时 HTTP 请求),否则所有 goroutine 会卡住等待
sync.Once 和全局变量组合的正确姿势
标准写法要同时满足:线程安全、非空保障、无重复初始化。典型结构如下:
立即学习“go语言免费学习笔记(深入)”;
type Config struct{ Host string }
var (
configOnce sync.Once
config *Config
configErr error
)
func LoadConfig() (*Config, error) {
configOnce.Do(func() {
c, err := loadFromEnv() // 自定义加载逻辑
config, configErr = c, err
})
return config, configErr
}
func loadFromEnv() (*Config, error) {
host := os.Getenv("HOST")
if host == "" {
return nil, errors.New("missing HOST env")
}
return &Config{Host: host}, nil
}
-
config和configErr必须是包级变量,且类型一致(不能一个是指针一个是值) - 不要把
LoadConfig做成无错误返回的函数,否则错误会被吞掉 - 多次调用
LoadConfig()不会重复执行loadFromEnv,但会每次都返回相同结果(含 error)
和 init() 函数对比:什么情况该选 sync.Once
init() 在包加载时执行,无法延迟、无法按需、无法传参、无法重试;sync.Once 则适合:
- 依赖运行时配置(如 flag、env、配置中心)的初始化
- 需要首次调用才触发的资源加载(如打开文件、连接 DB)
- 可能失败需由上层决定是否重试的场景(
init()失败会导致整个程序 crash) - 测试时需 mock 初始化行为(
init()无法打桩)
反过来说:如果初始化绝对稳定、无外部依赖、且必须启动即就绪(如常量映射表),用 init() 更简洁。
容易被忽略的内存可见性细节
sync.Once 内部用了 atomic 和 mutex,能保证 Do 中写入的变量对后续读取 goroutine 可见——但仅限于「Do 执行完毕后」。例如:
var data map[string]int
var once sync.Once
func initMap() {
once.Do(func() {
data = make(map[string]int) // ✅ 这个赋值对之后所有 goroutine 可见
data["a"] = 1 // ✅ 这个写入也可见(因为 map 是引用类型)
})
}
但如果写的是非指针/非引用类型(如 int、struct 值),且后续要修改其字段,则需额外同步机制:
- 避免在
Do外直接修改已初始化的值类型字段(无锁下不安全) - 若必须修改,要么用
sync.Mutex保护,要么把值包装成指针或原子类型 -
sync.Once不提供读写锁能力,它只是“一次性写屏障”
真正难的不是写对那几行代码,而是想清楚:这个单例到底要不要支持运行时重载?它的状态变更是否需要同步?一旦混用 sync.Once 和其他并发原语,debug 成本会陡增。


















