理解单例:
在程序运行的周期内,只初始化一次,称为单例
在go中,我们可以使用两种方法来实现单例。
- init 方法
- sync.Once
init非常地简单,在程序运行的时候,提前把我们需要的资源初始化好,就可以实现一个简单单例了。
但是在程序的其他地方,如何控制只实现一次?
这里就需要使用到sync.Once
.
先看单例的实现
type Singleton struct {
t int64
}
var once sync.Once
var singleton *Singleton // 单例
func GetInstance() *Singleton {
once.Do(func() {
t := time.Now().UnixNano()
singleton = &Singleton{
t: t,
}
})
return singleton
}
上面就实现了一个单例
为何sync.Once可以实现
先来看看go 1.13.5下Once的实现
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()) {
// Note: Here is an incorrect implementation of Do:
//
// if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
// f()
// }
//
// Do guarantees that when it returns, f has finished.
// This implementation would not implement that guarantee:
// given two simultaneous calls, the winner of the cas would
// call f, and the second would return immediately, without
// waiting for the first's call to f to complete.
// This is why the slow path falls back to a mutex, and why
// the atomic.StoreUint32 must be delayed until after f returns.
if atomic.LoadUint32(&o.done) == 0 {
// Outlined slow-path to allow inlining of the fast-path.
o.doSlow(f)
}
}
Do
里面的注释非常有意思,不仅说明了如何实现只执行一次,还给出了一个错误的示范。
在Do
的实现中,会去判断done
的值是否是0,如果是0的话,才可以进行初始化。
查看doSlow(f func())
的实现
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
这里面进来就会加一个互斥锁,然后又一次判断done
的值是0,才开始将传入的方法f()
进行执行。再后面一步是把done
的值设置为1,再释放掉互斥锁。
在生命周期内,done
== 1,一直存在,所以f()
就不会被再次执行。
验证sync.Once
的效果
package singleton
import (
"sync"
"time"
)
type Singleton struct {
t int64
}
type NSingleton struct {
t int64
}
var once sync.Once
var singleton *Singleton // 单例
var nSingleton *NSingleton // 非单例
func GetInstance() *Singleton {
once.Do(func() {
t := time.Now().UnixNano()
singleton = &Singleton{
t: t,
}
})
return singleton
}
func GetNSingletonInstance() *NSingleton {
t := time.Now().UnixNano()
nSingleton = &NSingleton{
t: t,
}
return nSingleton
}
测试文件
package singleton
// go test singleton_test.go singleton.go -json
import (
"testing"
"time"
)
func TestGetInstance(t *testing.T) {
t1 := GetInstance().t
time.Sleep(time.Second * 2)
t2 := GetInstance().t
t.Log(t1, t2)
if t1 == t2 {
t.Log("singleton ok")
} else {
t.Error("singleton err")
}
}
func TestGetNSingletonInstance(t *testing.T) {
t1 := GetNSingletonInstance().t
time.Sleep(time.Second * 2)
t2 := GetNSingletonInstance().t
t.Log(t1, t2)
if t1 == t2 {
t.Error("n singleton ok")
} else {
t.Log("n singleton err")
}
}