推荐使用 sync.Once 实现线程安全单例,它保证初始化函数仅执行一次且并发安全;避免双重检查锁,因 Go 内存模型不支持 volatile 且 sync.Once 更可靠高效;支持懒汉式与饿汉式两种方式。

在 Go 语言中实现线程安全的单例模式,核心是避免多次初始化、防止竞态,并兼顾性能与简洁性。推荐使用 sync.Once —— 它天然保证函数只执行一次,且并发安全,无需手动加锁。
用 sync.Once 实现懒汉式线程安全单例
sync.Once 是 Go 标准库提供的轻量级同步原语,适合单次初始化场景。它内部已做充分优化,比手写互斥锁更高效、更可靠。
- 定义一个私有全局变量(如
instance *Singleton)和一个sync.Once实例 - 提供公有获取方法(如
GetInstance()),内部调用once.Do()包裹初始化逻辑 - 初始化函数只会在第一次调用时执行,后续调用直接返回已创建的实例
示例代码:
package singleton
import "sync"
type Singleton struct {
// 可添加字段,如配置、连接池等
}
var (
instance *Singleton
once sync.Once
)
func GetInstance() *Singleton {
once.Do(func() {
instance = &Singleton{}
// 此处可加入耗时初始化操作(如加载配置、建立数据库连接)
})
return instance
}
避免常见陷阱:不要用双重检查锁(Double-Check Locking)
有些开发者尝试模仿 Java 的 DCL 写法(先判空 → 加锁 → 再判空),但在 Go 中既不必要也不推荐:
立即学习“go语言免费学习笔记(深入)”;
- Go 的内存模型不保证普通变量读写的重排序行为完全可控,DCL 需要
volatile或显式屏障,而 Go 没有volatile -
sync.Once已由标准库严格验证,正确处理了内存可见性和执行顺序 - DCL 增加复杂度却未带来收益,反而易出错
如果需要带参数的单例(如依赖注入场景)
Go 不支持构造函数重载,但可通过闭包或初始化函数封装参数。关键仍是确保“仅初始化一次”:
- 将参数传入初始化函数,或通过闭包捕获外部变量
- 仍用
sync.Once控制执行时机 - 注意:参数应在首次调用前就确定好,否则可能引发逻辑错误
例如:
var (
instance *Singleton
once sync.Once
config Config // 外部配置,需提前设置
)
func InitWithConfig(c Config) *Singleton {
once.Do(func() {
instance = &Singleton{config: c}
})
return instance
}
补充:饿汉式单例(编译期初始化)
若实例创建开销小、无依赖外部资源,也可用包级变量直接初始化:
var instance = &Singleton{}
func GetInstance() *Singleton {
return instance
}
这种方式天然线程安全(变量初始化在 init() 阶段完成,由 Go 运行时保证),但缺乏懒加载特性,不适合初始化成本高或依赖运行时环境的场景。










