首页 > 编程笔记 > Go语言笔记 阅读:2

Golang sync.Once实现单例模式(附带实例)

在日常业务场景中,有很多全局对象都只能初始化一次,比如说程序中的数据库客户端对象、Redis客户端对象等。

如何保证这些对象只初始化一次呢?一种方案是,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
......

参考上面的输出结果,每次输出的客户端对象地址都是一样的,说明这些客户端对象都是同一个对象,即通过 sync.Once 可以实现单例模式。当然,sync.Once 内部其实也是通过互斥锁实现的,有兴趣的读者可以自行研究,这里就不再赘述。

相关文章