Go单例靠sync.Once实现线程安全、延迟初始化的全局唯一实例,避免包级变量早初始化缺陷;不支持传参构造,需用选项模式或依赖注入;测试时应可替换实例以隔离状态。
Go 语言里没有“类”和“构造函数”,所谓单例不是靠语法限制,而是靠包级变量 + 同步控制来保证全局唯一实例。直接暴露一个 GetInstance() 函数是最常见、最安全的做法。
用 sync.Once 保证初始化只执行一次
这是 Go 单例最主流的实现方式。它天然线程安全,且延迟初始化(第一次调用才创建),避免包初始化阶段的副作用或依赖未就绪问题。
常见错误是手动加锁做双重检查(Double-Check Locking),在 Go 中完全没必要,反而容易出错——sync.Once 就是为此设计的。
-
sync.Once内部已处理内存可见性与竞态,无需额外atomic或mutex - 初始化函数(
once.Do()里传入的)只会被执行一次,哪怕多个 goroutine 同时调用GetInstance() - 如果初始化过程 panic,后续调用仍会 panic;需确保初始化逻辑健壮
var (
instance *DBClient
once sync.Once
)
func GetInstance() *DBClient {
once.Do(func() {
instance = &DBClient{conn: connectToDB()}
})
return instance
}
为什么不用包级变量直接初始化?
像 var instance = &DBClient{...} 这样写看似简单,但隐患明显: