什么是 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}`
扫码关注
获取更多干货内容