单例模式(singleton),是保证一个类只有一个实例,并提供一个访问它的全局访问点,确保了在全局范围内只有一个实例,避免了多次创建实例和资源浪费。 常用场景有应用程序的日志应用,Web应用的配置对象的读取,数据库连接池的设计等。
优点:
- 提供了对唯一实例的受控访问
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式可以提高系统性能
缺点:
- 由于单例模式没有抽象层,因此单例类的扩展有很大的困难
- 单例类的职责过重,在一定程度上违背了单一职责原则
type Singleton struct {}
var instance *Singleton
var once sync.Once
func GetInstance() *Singleton{
once.Do(func() {
instance = &Singleton{}
})
return instance
}
定义了一个名为Singleton的空结构体,该结构体没有任何字段。代码中还定义了一个名为instance的指针变量,用于保存Singleton的唯一实例。同时,还有一个名为once的sync.Once类型变量,用于确保GetInstance函数只被执行一次。
GetInstance函数是一个工厂方法,用于获取Singleton的实例。在函数内部,使用once.Do方法来保证instance只被初始化一次。当GetInstance函数第一次被调用时,once.Do方法会执行传入的匿名函数,该匿名函数会创建一个Singleton的实例,并将其赋值给instance。之后,每次调用GetInstance函数时,都会直接返回已经创建好的instance实例。 这种实现方式可以确保在多线程环境下,只有一个Singleton的实例被创建,并且能够提供全局访问点来获取该实例。
sync.Once 是 Golang package 中使方法只执行一次的对象实现,作用与 init 函数类似。但也有所不同。
- init函数是在文件包首次被加载的时候执行,且只执行一次
- sync.Onc是在代码运行中需要的时候执行,且只执行一次
当一个函数不希望程序在一开始的时候就被执行的时候,可以使用 sync.Once
函数 ReadConfig 需要读取环境变量,并转换为对应的配置。环境变量在程序执行前已经确定,执行过程中不会发生改变。ReadConfig 可能会被多个协程并发调用,为了提升性能(减少执行时间和内存占用),使用 sync.Once 是一个比较好的方式。
type Config struct {
Server string
Port int64
}
var (
once sync.Once
config *Config
)
func ReadConfig() *Config {
once.Do(func() {
var err error
config = &Config{Server: os.Getenv("TT_SERVER_URL")}
config.Port, err = strconv.ParseInt(os.Getenv("TT_PORT"), 10, 0)
if err != nil {
config.Port = 8080 // default port
}
log.Println("init config")
})
return config
}
func main() {
for i := 0; i < 10; i++ {
go func() {
_ = ReadConfig()
}()
}
time.Sleep(time.Second)
}
如果 ReadConfig 每次都构造出一个新的 Config 结构体,既浪费内存,又浪费初始化时间。如果 ReadConfig 中不加锁,初始化全局变量 config 就可能出现并发冲突。这种情况下,使用 sync.Once 既能够保证全局变量初始化时是线程安全的,又能节省内存和初始化时间。
$ go run .
2023/08/29 23:51:49 init config
init config 仅打印了一次,即 sync.Once 中的初始化函数仅执行了一次。