Go 的单例可以用 sync.Once 简单实现:
var gObject *ClassName
var once sync.Once
func GetInstance() *ClassName {
once.Do(func(){
gObject = &ClassName{}
})
return gObject
}
sync.Once 的代码如下:
package sync
import (
"sync/atomic"
)
type Once struct {
done uint32
m Mutex
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
-
正常的使用了锁,并且是”双重锁检查”模式
-
有两个奇怪的实现点:
- 为什么用
atomic.LoadUint32(&o.done)
而不用atomic.CompareAndSwapUint32(&o.done, 0, 1)
? Once 的语义是:”保证目标函数只执行一次”,如果这里在没有锁的情况下执行了,其他线程也可能执行,所以先不能设置”已执行”标识位。 - 为什么用
defer
更新o.done
而不直接更新? 如果在f()
后面放更新代码,如果发生了 panic,会导致更新代码没有走到;如果放在前面,实际上函数还没有执行,其他线程可能取得错误结果, 不符合”o.done 一旦设置就表示函数执行过一次”的语义
- 为什么用