-
sync.Once:sync包是golang提供的一个线程安全的同步包,once一次,once提供的Do方法中的f只会被调用一次,简便f出现了panic,再次调用once的Do方法,f也不会被执行
-
sync.Once使用示例
func main() {
once := sync.Once{}
for i := 0; i < 5; i++ {
once.Do(doSomething)
time.Sleep(time.Second)
}
fmt.Println("end")
}
func doSomething() {
fmt.Printf("do it, time_stamp:%d\n", time.Now().Unix())
}
输出结果:
do it, time_stamp:1559812858
end
once.Do被执行了5次,但是只有一个输出,也就是doSomething只被执行了一次,这样让我们很好的联想到了单例模式,多次执行只会创建一个对象
- 使用sync.Once实现单例模式
func main() {
//启动多个协程获取对象并且调用对象的打印内存地址的方法
for i := 0; i < 5; i++ {
go func() {
singleton := GetSingleton()
singleton.PrintAddress()
}()
}
//睡眠一下,避免go程未执行就终止了程序
time.Sleep(time.Second)
}
type singleton struct {
}
var st *singleton
var once sync.Once
//获取对象实例
func GetSingleton() *singleton {
if st != nil {
return st
}
once.Do(func() {
//模拟对象创建耗时操作
time.Sleep(time.Millisecond*10)
st = &singleton{}
})
return st
}
//打印内存地址
func (s *singleton) PrintAddress() {
fmt.Printf("%p \n", s)
}
输出结果:
0x59a1c8
0x59a1c8
0x59a1c8
0x59a1c8
0x59a1c8
输出结果都是一样的,对象的内存地址都一样,所以对象都是同一个,对象没有被重复创建。使用sync.Once很优雅的实现了单例模式,无需自己进行加锁等操作
4.源码分析
type Once struct {
m Mutex //互斥锁
done uint32 //f方法的执行标识,0未执行,1已执行
}
func (o *Once) Do(f func()) {
//done==1,表示f已经被执行了,直接返回
if atomic.LoadUint32(&o.done) == 1 {
return
}
//加锁
o.m.Lock()
//使用defer释放锁
defer o.m.Unlock()
//再次判断done是否已经执行,done==0表示未执行,双重校验,避免f多次执行
if o.done == 0 {
//执行f方法,执行完成后,done置为1,
//因为使用defer将done置为1,所以即便是f中panic了,done也会被置为1
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
5.小结
如果我们的代码中需要某部分代码只被执行一次,可以使用sync.once优雅的去实现。比如:程序启动时,初始化配置文件解析到对象当中,抑或着是使用单例模式都可以使用once很好的实现。