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
......