<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深入理解 sync.Once:單例模式的絕佳選擇

          共 538字,需瀏覽 2分鐘

           ·

          2020-08-12 02:06

          sync.Once是讓函數方法只被調用執(zhí)行一次的實現,其最常應用于單例模式之下,例如初始化系統(tǒng)配置、保持數據庫唯一連接等。

          sync.Once的單例模式示例


           1package?main
          2
          3import?(
          4????"fmt"
          5????"sync"
          6)
          7
          8type?Instance?struct{}
          9
          10var?(
          11????once?????sync.Once
          12????instance?*Instance
          13)
          14
          15func?NewInstance()?*Instance?{
          16????once.Do(func()?{
          17????????instance?=?&Instance{}
          18????????fmt.Println("Inside")
          19????})
          20????fmt.Println("Outside")
          21????return?instance
          22}
          23
          24func?main()?{
          25????for?i?:=?0;?i?3;?i++?{
          26????????_?=?NewInstance()
          27????}
          28}


          輸出


          1$?go?run?main.go?
          2Inside
          3Outside
          4Outside
          5Outside


          從上述例子可以看到,雖然多次調用NewInstance()函數,但是Once.Do()中的方法有且僅被執(zhí)行了一次。那么sync.Once是如何做到這一點的呢?

          sync.Once的源碼解析


          1type?Once?struct?{
          2????//?done?indicates?whether?the?action?has?been?performed.
          3????//?It?is?first?in?the?struct?because?it?is?used?in?the?hot?path.
          4????//?The?hot?path?is?inlined?at?every?call?site.
          5????//?Placing?done?first?allows?more?compact?instructions?on?some?architectures?(amd64/x86),
          6????//?and?fewer?instructions?(to?calculate?offset)?on?other?architectures.
          7????done?uint32
          8????m????Mutex
          9}


          Once結構體非常簡單,其中done是調用標識符,Once對象初始化時,其done值默認為0,Once僅有一個Do()方法,當Once首次調用Do()方法后,done值變?yōu)?。m作用于初始化競態(tài)控制,在第一次調用Once.Do()方法時,會通過m加鎖,以保證在第一個Do()方法中的參數f()函數還未執(zhí)行完畢時,其他此時調用Do()方法會被阻塞(不返回也不執(zhí)行)。


          Once.Do()方法的實現細節(jié)如下


           1func?(o?*Once)?Do(f?func())?{
          2????//?Note:?Here?is?an?incorrect?implementation?of?Do:
          3????//
          4????//??if?atomic.CompareAndSwapUint32(&o.done,?0,?1)?{
          5????//??????f()
          6????//??}
          7????//
          8????//?Do?guarantees?that?when?it?returns,?f?has?finished.
          9????//?This?implementation?would?not?implement?that?guarantee:
          10????//?given?two?simultaneous?calls,?the?winner?of?the?cas?would
          11????//?call?f,?and?the?second?would?return?immediately,?without
          12????//?waiting?for?the?first's?call?to?f?to?complete.
          13????//?This?is?why?the?slow?path?falls?back?to?a?mutex,?and?why
          14????//?the?atomic.StoreUint32?must?be?delayed?until?after?f?returns.
          15
          16????if?atomic.LoadUint32(&o.done)?==?0?{
          17????????//?Outlined?slow-path?to?allow?inlining?of?the?fast-path.
          18????????o.doSlow(f)
          19????}
          20}
          21
          22func?(o?*Once)?doSlow(f?func())?{
          23????o.m.Lock()
          24????defer?o.m.Unlock()
          25????if?o.done?==?0?{
          26????????defer?atomic.StoreUint32(&o.done,?1)
          27????????f()
          28????}
          29}


          Do()方法的入參是一個無參數輸入與返回的函數,當o.done值為0時,執(zhí)行doSlow()方法,為1則退出Do()方法。doSlow()方法很簡單:加鎖,再次檢查o.done值,執(zhí)行f(),原子操作將o.done值置為1,最后釋放鎖。


          注意事項


          1. 在官方示例代碼中,提到了一種錯誤實現Do()方法的方式。


          1func?(o?*Once)?Do(f?func())?{
          2????if?atomic.CompareAndSwapUint32(&o.done,?0,?1)?{
          3????????f()
          4????}
          5}


          當并發(fā)多次調用Do()方法時,第一個被執(zhí)行的Do()方法會將o.done值從0置為1,并執(zhí)行f(),其他的調用Do()方法會立即被返回。這種處理方式和加鎖的方式會有所不同:它不能保證在第一個調用執(zhí)行Do()方法中的f()函數被執(zhí)行完畢之前,其他的f()函數會阻塞等待。


           1package?main
          2
          3import?(
          4????"fmt"
          5????"sync"
          6????"time"
          7)
          8
          9type?Config?struct?{}
          10
          11func?(c?*Config)?init(filename?string)?{
          12????fmt.Printf("mock?[%s]?config?initial?done!\n",?filename)
          13}
          14
          15var?(
          16????once?sync.Once
          17????cfg??*Config
          18)
          19
          20func?main()?{
          21????cfg?=?&Config{}
          22
          23????go?once.Do(func()?{
          24????????time.Sleep(3?*?time.Second)
          25????????cfg.init("first?file?path")
          26????})
          27
          28????time.Sleep(time.Second)
          29????once.Do(func()?{
          30????????time.Sleep(time.Second)
          31????????cfg.init("second?file?path")
          32????})
          33????fmt.Println("運行到這里!")
          34????time.Sleep(5?*?time.Second)
          35}


          輸出


          1$?go?run?main.go?
          2mock?[first?file?path]?config?initial?done!
          3運行到這里!


          可以看到第二次調用once.Do()時候,其輸入參數f()函數雖然沒有被執(zhí)行,但是整個Do()是被阻塞的(被阻塞于o.m.Lock()處),它需要等待首次調用once.Do()執(zhí)行完畢,才會退出阻塞狀態(tài)。而錯誤實現Do()方法的方式,就無法保證此規(guī)則的實現。


          2. 避免死鎖


           1package?main
          2
          3import?(
          4????"fmt"
          5????"sync"
          6)
          7
          8func?main()?{
          9????once?:=?sync.Once{}
          10????once.Do(func()?{
          11????????fmt.Println("outside?call")
          12????????once.Do(func()?{
          13????????????fmt.Println("inside?call")
          14????????})
          15????})
          16}


          輸出


          1$?go?run?main.go?
          2outside?call
          3fatal?error:?all?goroutines?are?asleep?-?deadlock!


          注意,同樣由于o.m.Lock()處的代碼限定,once.Do()內部調用Do()方法時,會造成死鎖的發(fā)生。




          推薦閱讀



          學習交流 Go 語言,掃碼回復「進群」即可


          站長 polarisxu

          自己的原創(chuàng)文章

          不限于 Go 技術

          職場和創(chuàng)業(yè)經驗


          Go語言中文網

          每天為你

          分享 Go 知識

          Go愛好者值得關注



          瀏覽 312
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  啪啪啪网址 | 青娱乐在线视频成人 | 国产婷婷内射 | 亚洲精品影院 | 无码人妻精品一二三在线99绯色 |