Go中实现单例模式应使用sync.Once配合包级变量,确保线程安全、懒加载及错误处理;避免init()直接初始化或双重检查锁。

在 Go 中实现单例模式,核心目标是确保某个结构体或资源在整个程序生命周期中只被初始化一次,并提供线程安全的全局访问入口。Go 本身没有类和构造函数,但可通过包级变量 + sync.Once 高效、简洁地达成这一目标。
用 sync.Once 保证初始化仅执行一次
sync.Once 是 Go 标准库提供的轻量级同步原语,其 Do 方法可确保传入的函数最多执行一次,且并发调用时自动阻塞等待首次完成。这是实现线程安全单例最推荐的方式。
- 定义一个私有全局变量(如
var instance *Config)和一个sync.Once实例 - 封装一个导出的获取函数(如
func GetConfig() *Config),内部调用once.Do()触发初始化逻辑 - 初始化函数中创建实例并赋值给全局变量,后续调用直接返回该变量
避免使用 init() 或包级变量直接初始化
虽然可以写 var instance = NewResource(),但这会在包导入时立即执行构造,无法控制初始化时机,也不支持带参数或可能失败的初始化(如连接数据库、读配置文件)。而 sync.Once 方式支持懒加载、按需初始化,且能自然处理错误(例如在初始化函数中返回 error 并缓存)。
支持带依赖或错误处理的单例构造
若单例依赖外部服务(如 Redis 客户端、日志器、配置源),可在初始化函数中完成依赖注入与校验。常见做法是将错误状态缓存,或通过额外导出函数(如 Init() error)显式触发并检查结果,避免 GetXXX() 隐式失败。
立即学习“go语言免费学习笔记(深入)”;
- 不建议在
GetXXX()内 panic 或忽略错误;应返回指针 + error,或设计为“必须先 Init 成功才能 Get” - 若初始化失败,
sync.Once仍只执行一次,后续调用不会重试——这是预期行为,需由上层保障初始化成功
注意:不要用全局指针+双重检查锁(DCL)
有些开发者尝试模仿 Java 的双重检查锁写法(判空 → 加锁 → 再判空 → 初始化),这在 Go 中不仅没必要,还容易因内存模型和编译器优化引入隐患。Go 的 sync.Once 已内部处理了所有同步细节,更安全、更简洁、更符合 Go 的惯用法。










