Golang sync.Once实现单例模式(附带实例)
在日常业务场景中,有很多全局对象都只能初始化一次,比如说程序中的数据库客户端对象、Redis客户端对象等。
如何保证这些对象只初始化一次呢?一种方案是,Go程序启动时初始化这些全局对象,即不管后续会不会使用这些全局对象,都先初始化了。
另一种方案是,在初次使用这些对象时再初始化,但是当多个用户协程同时初始化对象时,还能保证这些对象只初始化一次吗?当然是可以的,比如通过互斥锁实现,初始化的时候先加锁,再检测对象是否已经初始化:如果已经初始化则直接返回;否则初始化该对象。
其实 Go 语言本身也提供了单例方案 sync.Once,通过 sync.Once 可以保证对象只初始化一次,使用方式如下:
另外,在 main 函数中创建了 10 个协程,并发地通过函数 NewClient 获取客户端对象,然后输出客户端对象的地址,以验证这些对象是否是同一个对象。
执行上面的程序后,输出结果如下所示:
如何保证这些对象只初始化一次呢?一种方案是,Go程序启动时初始化这些全局对象,即不管后续会不会使用这些全局对象,都先初始化了。
另一种方案是,在初次使用这些对象时再初始化,但是当多个用户协程同时初始化对象时,还能保证这些对象只初始化一次吗?当然是可以的,比如通过互斥锁实现,初始化的时候先加锁,再检测对象是否已经初始化:如果已经初始化则直接返回;否则初始化该对象。
其实 Go 语言本身也提供了单例方案 sync.Once,通过 sync.Once 可以保证对象只初始化一次,使用方式如下:
package main
import (
"fmt"
"sync"
"time"
)
type Client struct {
}
var client *Client
var once sync.Once
func main() {
for i := 0; i < 10; i+++ {
go func() {
c := NewClient()
fmt.Println(fmt.Sprintf("c addr:%p", c))
}()
}
time.Sleep(time.Second)
}
func NewClient() *Client {
// 如果已初始化直接返回
if client != nil {
return client
}
// 保证只初始化一次
once.Do(func() {
client = &Client{}
})
return client
}
在上面的代码中,函数 NewClient 用于获取自定义客户端对象。可以看到,该函数首先判断全局变量 client 是否等于 nil:如果不等于 nil,说明全局客户端对象已经初始化,直接返回;否则,通过 sync.Once 初始化全局客户端对象,这样就能保证只初始化一次。另外,在 main 函数中创建了 10 个协程,并发地通过函数 NewClient 获取客户端对象,然后输出客户端对象的地址,以验证这些对象是否是同一个对象。
执行上面的程序后,输出结果如下所示:
c addr:0x1165fe0
c addr:0x1165fe0
c addr:0x1165fe0
......
ICP备案:
公安联网备案: