什么是sync.Once
官网文档对它的形容是:一个对象将齐全执行一次,不得复制。经常用来单例对象的初始化场景,或者并发拜访只须要初始化一次的共享资源。sync.Once只裸露了一个办法Do,能够屡次调用,然而只有第一次调用Do办法时f参数才会执行,这里的f是一个无参数无返回值的函数。上面来看下官网给进去的demo:
`1package main` `2` `3import (` `4 "fmt"` `5 "sync"` `6)` `7` `8func main() {` `9 var once sync.Once``10 onceBody := func() {``11 fmt.Println("Only once")``12 }``13 done := make(chan bool)``14 for i := 0; i < 10; i++ {``15 go func() {``16 once.Do(onceBody)``17 done <- true``18 }()``19 }``20 for i := 0; i < 10; i++ {``21 <-done``22 }``23}``24// 后果只打印一次:only once`
源码剖析
sync.Once的源代码还很少的,间接在代码外面作剖析
`1package sync` `2` `3import (` `4 "sync/atomic"` `5)` `6` `7type Once struct {` `8 done uint32 // 初始值为0示意还未执行过,1示意曾经执行过` `9 m Mutex // m为互斥锁``10}``11``12func (o *Once) Do(f func()) {``13 // 判断done是否为0.若为0,示意未执行过,调用doSlow办法``14 if atomic.LoadUint32(&o.done) == 0 {``15 o.doSlow(f)``16 }``17}``18``19func (o *Once) doSlow(f func()) {``20 // 互斥锁加锁解锁``21 o.m.Lock()``22 defer o.m.Unlock()``23 // 加锁判断done是否为0``24 if o.done == 0 {``25 // 执行完f()函数后,将done值设置为1``26 defer atomic.StoreUint32(&o.done, 1)``27 f()``28 }``29}`
Do办法流程图:
疑难1:为什么在do办法的时候没有用到cas原子判断?
在do办法源码正文中的时候有这么一段话,说如果是应用 atomic.CompareAndSwapUint32(&o.done, 0, 1)此办法不会保障实现f函数调用实现。为什么会这么说,同时进行两次调用,cas的获胜者将调用f函数,失败者将立刻返回,而无需期待第一个对f的调用实现。此时f函数还在执行过程中,你就曾经回复了once.Do胜利了,此时全局变量还没有创立进去,行为是无奈定义的。那么怎么解决呢?有以下两个思路:
1)热门路:用原子读done的值,保障竞态条件正确;
2)加锁:既然不能用cas原子操作,那就用加锁的形式来保障原子性,如果done==0,那么走慢门路,先加锁,而后在执行f函数,最初将done设置为1,并解锁。
重点:千万不要把同一sync.Once用在嵌套构造中,这样非常容易造成死锁!
`1func once() {``2 once := &sync.Once{}``3 once.Do(func() {``4 once.Do(func() {``5 fmt.Println("test nestedDo")``6 })``7 })``8 //输入:fatal error: all goroutines are asleep - deadlock!``9}`
sync.Once实现单例模式
单例模式能够说是设计模式外面最简略的了,Go能够借助sync.Once来实现,代码如下:
`1package main` `2` `3import (` `4 "fmt"` `5 "sync"` `6)` `7` `8type Singleton struct{}` `9``10var (``11 singleton *Singleton``12 once sync.Once``13)``14``15func GetSingleton() *Singleton {``16 once.Do(func() {``17 fmt.Println("Create Obj")``18 singleton = new(Singleton)``19 })``20 return singleton``21}``22``23func main() {``24 var wg sync.WaitGroup``25 for i := 0; i < 5; i++ {``26 wg.Add(1)``27 go func() {``28 obj := GetSingleton()``29 fmt.Printf("%p\n", obj)``30 wg.Done()``31 }()``32 }``33 wg.Wait()``34 // 只打印一次 Create Obj``35}`
扫码关注
获取更多干货内容