在 Go 语言中,单例模式的实现与线程安全(Go 中称为 协程安全 或 并发安全)可以通过多种方式实现。以下是几种常见的方法:
1. 使用 sync.Once
(推荐)
sync.Once
是 Go 标准库中专门设计用来确保某个操作只执行一次的机制,非常适合实现单例模式。
package singleton
import "sync"
type Singleton struct {
// 单例对象的字段
}
var (
instance *Singleton
once sync.Once
)
func GetInstance() *Singleton {
once.Do(func() {
instance = &Singleton{}
// 初始化单例对象的字段
})
return instance
}
原理:
once.Do
保证内部的初始化函数只会执行一次,无论有多少协程同时调用GetInstance
。- 线程安全,无需手动管理锁。
优点:
- 简洁高效,无锁竞争。
- 延迟初始化(懒加载),只有在首次调用时创建实例。
2. 使用互斥锁(sync.Mutex
)
通过互斥锁确保初始化过程线程安全:
package singleton
import "sync"
type Singleton struct {
// 单例对象的字段
}
var (
instance *Singleton
mu sync.Mutex
)
func GetInstance() *Singleton {
if instance == nil { // 第一次检查,避免不必要的锁竞争
mu.Lock()
defer mu.Unlock()
if instance == nil { // 第二次检查,确保实例未被其他协程创建
instance = &Singleton{}
// 初始化单例对象的字段
}
}
return instance
}
原理:
- 双重检查锁定(Double-Checked Locking)减少锁的竞争。
- 第一次检查避免每次调用都加锁,第二次检查确保实例唯一。
缺点:
- 代码稍复杂,需要手动管理锁。
3. 包级变量直接初始化
如果单例对象无需延迟初始化,可以直接在包初始化时创建:
package singleton
type Singleton struct {
// 单例对象的字段
}
var instance = &Singleton{
// 初始化字段
}
func GetInstance() *Singleton {
return instance
}
原理:
- Go 的包初始化是线程安全的,
instance
会在程序启动时自动创建。 - 适合简单场景,但无法延迟初始化。
缺点:
- 如果初始化耗时,可能影响程序启动速度。
4. 利用 init
函数
在 init
函数中初始化单例:
package singleton
type Singleton struct {
// 单例对象的字段
}
var instance *Singleton
func init() {
instance = &Singleton{}
// 初始化字段
}
func GetInstance() *Singleton {
return instance
}
原理:
init
函数在包被导入时自动执行,且只会执行一次。- 线程安全,但无法延迟初始化。
5. 避免单例模式(依赖注入)
在 Go 中,单例模式有时被认为是反模式。更好的做法是通过 依赖注入 或 全局配置 来管理共享资源:
// 将单例作为参数传递,而非全局变量
type Singleton struct {
// 字段
}
func NewSingleton() *Singleton {
return &Singleton{}
}
// 使用依赖注入
func Process(s *Singleton) {
// 使用单例对象
}
优点:
- 避免全局状态,提升代码可测试性和可维护性。
总结
- 推荐方式:使用
sync.Once
,简洁高效且线程安全。 - 延迟初始化:优先选择
sync.Once
或双重检查锁定。 - 简单场景:直接使用包级变量或
init
函数。 - 最佳实践:尽量避免全局单例,优先通过依赖注入管理共享资源。
示例(完整版):
package singleton
import (
"sync"
)
type singleton struct {
// 单例对象的字段
Value int
}
var (
instance *singleton
once sync.Once
)
func GetInstance() *singleton {
once.Do(func() {
instance = &singleton{
Value: 42, // 初始化字段
}
})
return instance
}