亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Golang并發(fā)利器sync.Once的用法詳解

 更新時(shí)間:2023年04月12日 08:20:58   作者:陳明勇  
在某些場(chǎng)景下,我們需要初始化一些資源。有時(shí)會(huì)采用延遲初始化的方式,在真正需要資源的時(shí)候才進(jìn)行初始化。在這種情況下,Go語(yǔ)言中的sync.Once提供一個(gè)優(yōu)雅且并發(fā)安全的解決方案,本文將對(duì)其進(jìn)行詳細(xì)介紹

簡(jiǎn)介

在某些場(chǎng)景下,我們需要初始化一些資源,例如單例對(duì)象、配置等。實(shí)現(xiàn)資源的初始化有多種方法,如定義 package 級(jí)別的變量、在 init 函數(shù)中進(jìn)行初始化,或者在 main 函數(shù)中進(jìn)行初始化。這三種方式都能確保并發(fā)安全,并在程序啟動(dòng)時(shí)完成資源的初始化。

然而,有時(shí)我們希望采用延遲初始化的方式,在我們真正需要資源的時(shí)候才進(jìn)行初始化,這種需要確保并發(fā)安全,在這種情況下,Go 語(yǔ)言中的 sync.Once 提供一個(gè)優(yōu)雅且并發(fā)安全的解決方案,本文將對(duì)其進(jìn)行介紹。

sync.Once 基本概念

什么是 sync.Once

sync.OnceGo 語(yǔ)言中的一種同步原語(yǔ),用于確保某個(gè)操作或函數(shù)在并發(fā)環(huán)境下只被執(zhí)行一次。它只有一個(gè)導(dǎo)出的方法,即 Do,該方法接收一個(gè)函數(shù)參數(shù)。在 Do 方法被調(diào)用后,該函數(shù)將被執(zhí)行,而且只會(huì)執(zhí)行一次,即使在多個(gè)協(xié)程同時(shí)調(diào)用的情況下也是如此。

sync.Once 的應(yīng)用場(chǎng)景

sync.Once 主要用于以下場(chǎng)景:

  • 單例模式:確保全局只有一個(gè)實(shí)例對(duì)象,避免重復(fù)創(chuàng)建資源。
  • 延遲初始化:在程序運(yùn)行過(guò)程中需要用到某個(gè)資源時(shí),通過(guò) sync.Once 動(dòng)態(tài)地初始化該資源。
  • 只執(zhí)行一次的操作:例如只需要執(zhí)行一次的配置加載、數(shù)據(jù)清理等操作。

sync.Once 應(yīng)用實(shí)例

單例模式

在單例模式中,我們需要確保一個(gè)結(jié)構(gòu)體只被初始化一次。使用 sync.Once 可以輕松實(shí)現(xiàn)這一目標(biāo)。

package main

import (
   "fmt"
   "sync"
)

type Singleton struct{}

var (
   instance *Singleton
   once     sync.Once
)

func GetInstance() *Singleton {
   once.Do(func() {
      instance = &Singleton{}
   })
   return instance
}

func main() {
   var wg sync.WaitGroup

   for i := 0; i < 5; i++ {
      wg.Add(1)
      go func() {
         defer wg.Done()
         s := GetInstance()
         fmt.Printf("Singleton instance address: %p\n", s)
      }()
   }

   wg.Wait()
}

上述代碼中,GetInstance 函數(shù)通過(guò) once.Do() 確保 instance 只會(huì)被初始化一次。在并發(fā)環(huán)境下,多個(gè)協(xié)程同時(shí)調(diào)用 GetInstance 時(shí),只有一個(gè)協(xié)程會(huì)執(zhí)行 instance = &Singleton{},所有協(xié)程得到的實(shí)例 s 都是同一個(gè)。

延遲初始化

有時(shí)候希望在需要時(shí)才初始化某些資源。使用 sync.Once 可以實(shí)現(xiàn)這一目標(biāo)。

package main

import (
   "fmt"
   "sync"
)

type Config struct {
   config map[string]string
}

var (
   config *Config
   once   sync.Once
)

func GetConfig() *Config {
   once.Do(func() {
      fmt.Println("init config...")
      config = &Config{
         config: map[string]string{
            "c1": "v1",
            "c2": "v2",
         },
      }
   })
   return config
}

func main() {
   // 第一次需要獲取配置信息,初始化 config
   cfg := GetConfig()
   fmt.Println("c1: ", cfg.config["c1"])

   // 第二次需要,此時(shí) config 已經(jīng)被初始化過(guò),無(wú)需再次初始化
   cfg2 := GetConfig()
   fmt.Println("c2: ", cfg2.config["c2"])
}

在這個(gè)示例中,定義了一個(gè) Config 結(jié)構(gòu)體,它包含一些設(shè)置信息。使用 sync.Once 來(lái)實(shí)現(xiàn) GetConfig 函數(shù),該函數(shù)在第一次調(diào)用時(shí)初始化 Config。這樣,我們可以在真正需要時(shí)才初始化 Config,從而避免不必要的開(kāi)銷。

sync.Once 實(shí)現(xiàn)原理

type Once struct {
   // 表示是否執(zhí)行了操作
   done uint32
   // 互斥鎖,確保多個(gè)協(xié)程訪問(wèn)時(shí),只能一個(gè)協(xié)程執(zhí)行操作
   m    Mutex
}

func (o *Once) Do(f func()) {
   // 判斷 done 的值,如果是 0,說(shuō)明 f 還沒(méi)有被執(zhí)行過(guò)
   if atomic.LoadUint32(&o.done) == 0 {
      // 構(gòu)建慢路徑(slow-path),以允許對(duì) Do 方法的快路徑(fast-path)進(jìn)行內(nèi)聯(lián)
      o.doSlow(f)
   }
}

func (o *Once) doSlow(f func()) {
   // 加鎖
   o.m.Lock()
   defer o.m.Unlock()
   // 雙重檢查,避免 f 已被執(zhí)行過(guò)
   if o.done == 0 {
      // 修改 done 的值
      defer atomic.StoreUint32(&o.done, 1)
      // 執(zhí)行函數(shù)
      f()
   }
}

sync.Once 結(jié)構(gòu)體包含兩個(gè)字段:donemudone 是一個(gè) uint32 類型的變量,用于表示操作是否已經(jīng)執(zhí)行過(guò);m 是一個(gè)互斥鎖,用于確保在多個(gè)協(xié)程訪問(wèn)時(shí),只有一個(gè)協(xié)程能執(zhí)行操作。

sync.Once 結(jié)構(gòu)體包含兩個(gè)方法:DodoSlow。Do 方法是其核心方法,它接收一個(gè)函數(shù)參數(shù) f。首先它會(huì)通過(guò)原子操作atomic.LoadUint32(保證并發(fā)安全) 檢查 done 的值,如果為 0,表示 f 函數(shù)沒(méi)有被執(zhí)行過(guò),然后執(zhí)行 doSlow 方法。

doSlow 方法里,首先對(duì)互斥鎖 m 進(jìn)行加鎖,確保在多個(gè)協(xié)程訪問(wèn)時(shí),只有一個(gè)協(xié)程能執(zhí)行 f 函數(shù)。接著再次檢查 done 變量的值,如果 done 的值仍為 0,說(shuō)明 f 函數(shù)沒(méi)有被執(zhí)行過(guò),此時(shí)執(zhí)行 f 函數(shù),最后通過(guò)原子操作 atomic.StoreUint32done 變量的值設(shè)置為 1。

為什么會(huì)封裝一個(gè) doSlow 方法

doSlow 方法的存在主要是為了性能優(yōu)化。將慢路徑(slow-path)代碼從 Do 方法中分離出來(lái),使得 Do 方法的快路徑(fast-path)能夠被內(nèi)聯(lián)(inlined),從而提高性能。

為什么會(huì)有雙重檢查(double check)的寫法

從源碼可知,存在兩次對(duì) done 的值的判斷。

  • 第一次檢查:在獲取鎖之前,先使用原子加載操作 atomic.LoadUint32 檢查 done 變量的值,如果 done 的值為 1,表示操作已執(zhí)行,此時(shí)直接返回,不再執(zhí)行 doSlow 方法。這一檢查可以避免不必要的鎖競(jìng)爭(zhēng)。
  • 第二次檢查:獲取鎖之后,再次檢查 done 變量的值,這一檢查是為了確保在當(dāng)前協(xié)程獲取鎖期間,其他協(xié)程沒(méi)有執(zhí)行過(guò) f 函數(shù)。如果 done 的值仍為 0,表示 f 函數(shù)沒(méi)有被執(zhí)行過(guò)。

通過(guò)雙重檢查,可以在大多數(shù)情況下避免鎖競(jìng)爭(zhēng),提高性能。

加強(qiáng)的 sync.Once

sync.Once 提供的 Do 方法并沒(méi)有返回值,意味著如果我們傳入的函數(shù)如果發(fā)生 error 導(dǎo)致初始化失敗,后續(xù)調(diào)用 Do 方法也不會(huì)再初始化。為了避免這個(gè)問(wèn)題,我們可以實(shí)現(xiàn)一個(gè) 類似 sync.Once 的并發(fā)原語(yǔ)。

package main

import (
   "sync"
   "sync/atomic"
)


type Once struct {
   done uint32
   m    sync.Mutex
}

func (o *Once) Do(f func() error) error {
   if atomic.LoadUint32(&o.done) == 0 {
      return o.doSlow(f)
   }
   return nil
}

func (o *Once) doSlow(f func() error) error {
   o.m.Lock()
   defer o.m.Unlock()
   var err error
   if o.done == 0 {
      err = f()
      // 只有沒(méi)有 error 的時(shí)候,才修改 done 的值
      if err == nil {
         atomic.StoreUint32(&o.done, 1)
      }
   }
   return err
}

上述代碼實(shí)現(xiàn)了一個(gè)加強(qiáng)的 Once 結(jié)構(gòu)體。與標(biāo)準(zhǔn)的 sync.Once 不同,這個(gè)實(shí)現(xiàn)允許 Do 方法的函數(shù)參數(shù)返回一個(gè) error。如果執(zhí)行函數(shù)沒(méi)有返回 error,則修改 done 的值以表示函數(shù)已執(zhí)行。這樣,在后續(xù)的調(diào)用中,只有在沒(méi)有發(fā)生 error 的情況下,才會(huì)跳過(guò)函數(shù)執(zhí)行,避免初始化失敗。

sync.Once 的注意事項(xiàng)

死鎖

通過(guò)分析 sync.Once 的源碼,可以看到它包含一個(gè)名為 m 的互斥鎖字段。當(dāng)我們?cè)?Do 方法內(nèi)部重復(fù)調(diào)用 Do 方法時(shí),將會(huì)多次嘗試獲取相同的鎖。但是 mutex 互斥鎖并不支持可重入操作,因此這將導(dǎo)致死鎖現(xiàn)象。

func main() {
   once := sync.Once{}
   once.Do(func() {
      once.Do(func() {
         fmt.Println("init...")
      })
   })
}

初始化失敗

這里的初始化失敗指的是在調(diào)用 Do 方法之后,執(zhí)行 f 函數(shù)的過(guò)程中發(fā)生 error,導(dǎo)致執(zhí)行失敗,現(xiàn)有的 sync.Once 設(shè)計(jì)我們是無(wú)法感知到初始化的失敗的,為了解決這個(gè)問(wèn)題,我們可以實(shí)現(xiàn)一個(gè)類似 sync.Once 的加強(qiáng) once,前面的內(nèi)容已經(jīng)提供了具體實(shí)現(xiàn)。

小結(jié)

本文詳細(xì)介紹了 Go 語(yǔ)言中的 sync.Once,包括它的基本定義、使用場(chǎng)景和應(yīng)用實(shí)例以及源碼分析等。在實(shí)際開(kāi)發(fā)中,sync.Once 經(jīng)常被用于實(shí)現(xiàn)單例模式和延遲初始化操作。

雖然 sync.Once 簡(jiǎn)單而又高效,但是錯(cuò)誤的使用可能會(huì)造成一些意外情況,需要格外小心。

總之,sync.OnceGo 中非常實(shí)用的一個(gè)并發(fā)原語(yǔ),可以幫助開(kāi)發(fā)者實(shí)現(xiàn)各種并發(fā)場(chǎng)景下的安全操作。如果遇到只需要初始化一次的場(chǎng)景,sync.Once 是一個(gè)非常好的選擇。

以上就是Golang并發(fā)利器sync.Once的用法詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang sync.Once的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • golang等待觸發(fā)事件的實(shí)例

    golang等待觸發(fā)事件的實(shí)例

    這篇文章主要介紹了golang等待觸發(fā)事件的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • Go語(yǔ)言sync.Pool對(duì)象池使用場(chǎng)景基本示例

    Go語(yǔ)言sync.Pool對(duì)象池使用場(chǎng)景基本示例

    這篇文章主要為大家介紹了Go語(yǔ)言sync.Pool對(duì)象池使用場(chǎng)景的基本示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • golang接口的正確用法分享

    golang接口的正確用法分享

    這篇文章主要介紹了golang接口的正確用法分享的相關(guān)資料,需要的朋友可以參考下
    2023-09-09
  • web項(xiàng)目中g(shù)olang性能監(jiān)控解析

    web項(xiàng)目中g(shù)olang性能監(jiān)控解析

    這篇文章主要為大家介紹了web項(xiàng)目中g(shù)olang性能監(jiān)控詳細(xì)的解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • go自動(dòng)下載所有的依賴包go module使用詳解

    go自動(dòng)下載所有的依賴包go module使用詳解

    這篇文章主要介紹了go自動(dòng)下載所有的依賴包go module使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • Go+Redis實(shí)現(xiàn)常見(jiàn)限流算法的示例代碼

    Go+Redis實(shí)現(xiàn)常見(jiàn)限流算法的示例代碼

    限流是項(xiàng)目中經(jīng)常需要使用到的一種工具,一般用于限制用戶的請(qǐng)求的頻率,也可以避免瞬間流量過(guò)大導(dǎo)致系統(tǒng)崩潰,或者穩(wěn)定消息處理速率。這篇文章主要是使用Go+Redis實(shí)現(xiàn)常見(jiàn)的限流算法,需要的可以參考一下
    2023-04-04
  • Go語(yǔ)言interface 與 nil 的比較

    Go語(yǔ)言interface 與 nil 的比較

    在golang中,nil只能賦值給指針、channel、func、interface、map或slice類型的變量。如果未遵循這個(gè)規(guī)則,則會(huì)引發(fā)panic。
    2017-08-08
  • Go語(yǔ)言截取字符串函數(shù)用法

    Go語(yǔ)言截取字符串函數(shù)用法

    這篇文章主要介紹了Go語(yǔ)言截取字符串函數(shù)用法,實(shí)例分析了Go語(yǔ)言操作字符串的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • golang連接池檢查連接失敗時(shí)如何重試(示例代碼)

    golang連接池檢查連接失敗時(shí)如何重試(示例代碼)

    在Go中,可以通過(guò)使用database/sql包的DB類型的Ping方法來(lái)檢查數(shù)據(jù)庫(kù)連接的可用性,本文通過(guò)示例代碼,演示了如何在連接檢查失敗時(shí)進(jìn)行重試,感興趣的朋友一起看看吧
    2023-10-10
  • 基于go手動(dòng)寫個(gè)轉(zhuǎn)發(fā)代理服務(wù)的代碼實(shí)現(xiàn)

    基于go手動(dòng)寫個(gè)轉(zhuǎn)發(fā)代理服務(wù)的代碼實(shí)現(xiàn)

    這篇文章主要介紹了基于go手動(dòng)寫個(gè)轉(zhuǎn)發(fā)代理服務(wù)的代碼實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02

最新評(píng)論