Go sync.Once 使用指南:单次初始化的最佳实践


一、为什么需要 sync.Once?

在 Go 并发编程中,单次初始化是常见需求场景(如配置加载、数据库连接池创建等)。sync.Once 是 Go 标准库提供的线程安全工具,能确保某段代码逻辑在整个程序生命周期中仅执行一次,与 init() 函数的区别在于它支持懒加载模式。


二、基础用法与原理

  1. 核心方法
type Once struct {
    done uint32 // 原子操作标记位 
    m    Mutex  // 互斥锁 
}
 
func (o *Once) Do(f func()) 
  1. 极简示例
var (
    instance *Singleton 
    once     sync.Once 
)
 
func GetInstance() *Singleton {
    once.Do(func() {
        instance = &Singleton{Data: "已初始化"}
    })
    return instance 
}

-执行效果:无论并发调用多少次 GetInstance(),实例只会创建一次*


三、典型应用场景

场景 1:全局配置加载

var (
    appConfig *Config 
    loadOnce sync.Once 
)
 
func LoadConfig() *Config {
    loadOnce.Do(func() {
        data, _ := os.ReadFile("config.yaml")
        appConfig = parseYAML(data) // 真实项目需处理错误 
    })
    return appConfig 
}

场景 2:数据库连接池初始化

var (
    dbPool *sql.DB 
    dbOnce sync.Once 
)
 
func GetDB() *sql.DB {
    dbOnce.Do(func() {
        connStr := "user:pass@/dbname"
        dbPool, _ = sql.Open("mysql", connStr) 
        dbPool.SetMaxOpenConns(100)
    })
    return dbPool 
}

场景 3:单例模式实现

type Logger struct {
    // 日志器字段 
}
 
var (
    loggerInstance *Logger 
    loggerOnce     sync.Once 
)
 
func GetLogger() *Logger {
    loggerOnce.Do(func() {
        loggerInstance = &Logger{
            // 初始化字段 
        }
        loggerInstance.Init() 
    })
    return loggerInstance 
}

四、高级技巧与注意事项

  1. 错误处理新方案 使用新增的 sync.OnceValuesync.OnceValues 实现带错误返回:
var loadConfigOnce = sync.OnceValues(func() (*Config, error) {
    data, err := os.ReadFile("config.yaml")
    if err != nil {
        return nil, err 
    }
    return parseYAML(data)
})
 
func GetConfig() (*Config, error) {
    return loadConfigOnce()
}
  1. 闭包陷阱规避 (1.23版本后有变化)
// ❌ 错误写法(闭包捕获循环变量)
for _, url := range urls {
    go func() {
        once.Do(func() { 
            fetch(url) // 实际只会执行最后一次循环的url 
        })
    }()
}
 
// ✅ 正确写法 
for _, url := range urls {
    u := url // 创建局部变量副本 
    go func() {
        once.Do(func() { 
            fetch(u) 
        })
    }()
}
  1. 性能优化建议
  • 高频调用场景建议将 Once 实例声明为全局变量
  • 避免在 Do() 方法中执行耗时操作(会阻塞其他协程)
  • 重置需求可通过 atomic.StoreUint32(&once.done, 0) 实现(需谨慎使用)

如有疑问关注公众号给我留言
wx

关注公众号

©2017-2023 鲁ICP备17023316号-1 Powered by Hugo