单例模式的目的是为了保证一个类仅有一个实例,并提供一个访问它的全局访问点.
单例模式
单例模式的两种表现形式。
-
饿汉式:类加载时,就进行实例化。
-
懒汉式,第一次引用类时才进行实例化。
饿汉式
单例对象是在包加载时立即被创建,所以这个方式叫作饿汉式
singleton
包在被导入时会自动初始化 instance
实例,使用时通过调用 singleton.GetSingleton()
函数即可获得 singleton
这个结构体的单例对象。
1
2
3
4
5
6
7
8
9
|
package singleton
type singleton struct{}
var instance = &singleton{}
func GetSingleton() *singleton {
return instance
}
|
懒汉式
懒汉式模式下实例只有在第一次被使用时才被创建。
1
2
3
4
5
6
7
8
9
10
11
12
|
package singleton
type singleton struct{}
var instance *singleton
func GetSingleton() *singleton {
if instance == nil {
instance = &singleton{}
}
return instance
}
|
并发安全
懒汉式单例模式并发安全问题,比如像下面这样。
使用了锁机制也带来了一些问题,这让每次调用 GetSingleton()
时程序都会进行加锁、解锁的步骤,从而导致程序性能的下降。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package singleton
import "sync"
type singleton struct{}
var instance *singleton
var mu sync.Mutex
func GetSingleton() *singleton {
mu.Lock()
defer mu.Unlock()
if instance == nil {
instance = &singleton{}
}
return instance
}
|
sync/atomic
带锁的单例模式是一个不错的方法,但是还并不是很完美。如果使用 sync/atomic
包的话,我们可以原子化加载并设置一个标志,该标志表明我们是否已初始化实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import "sync"
import "sync/atomic"
var initialized uint32
...
func GetInstance() *singleton {
if atomic.LoadUInt32(&initialized) == 1 {
return instance
}
mu.Lock()
defer mu.Unlock()
if initialized == 0 {
instance = &singleton{}
atomic.StoreUint32(&initialized, 1)
}
return instance
}
|
不过这样有点繁琐,我们可以通过 Go 标准库 sync
包中提供的 Once
方法,让我们写出更加优雅的代码。
sync.Once
sync 库中的 Once
方法,它能保证某个操作仅且只执行一次。
Go标准库的部分源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
// Once is an object that will perform exactly one action.
type Once struct {
// done indicates whether the action has been performed.
// It is first in the struct because it is used in the hot path.
// The hot path is inlined at every call site.
// Placing done first allows more compact instructions on some architectures (amd64/x86),
// and fewer instructions (to calculate offset) on other architectures.
done uint32
m Mutex
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 { // check
// Outlined slow-path to allow inlining of the fast-path.
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock() // lock
defer o.m.Unlock()
if o.done == 0 { // check
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
|
once.Do()
的用法如下:
1
2
3
|
once.Do(func() {
// 在这里执行安全的初始化
})
|
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package singleton
import "sync"
type singleton struct{}
var instance *singleton
var once sync.Once
func GetSingleton() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}
|
Once
是一个结构体,在执行 Do
方法的内部通过 atomic
操作和加锁机制来保证并发安全,且 once.Do
能够保证多个 goroutine
同时执行时 &singleton{}
只被创建一次。
结论
以上几种单例模式的常用套路中,使用 sync.Once
包是安全地实现此目标的首选方式,sync.Once
包帮我们隐藏了部分细节,却可以让代码可读性得到很大提升。