中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Golang并發利器sync.Once怎么使用

發布時間:2023-05-08 15:39:05 來源:億速云 閱讀:103 作者:iii 欄目:開發技術

這篇文章主要介紹了Golang并發利器sync.Once怎么使用的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Golang并發利器sync.Once怎么使用文章都會有所收獲,下面我們一起來看看吧。

sync.Once 基本概念

什么是 sync.Once

sync.OnceGo 語言中的一種同步原語,用于確保某個操作或函數在并發環境下只被執行一次。它只有一個導出的方法,即 Do,該方法接收一個函數參數。在 Do 方法被調用后,該函數將被執行,而且只會執行一次,即使在多個協程同時調用的情況下也是如此。

sync.Once 的應用場景

sync.Once 主要用于以下場景:

  • 單例模式:確保全局只有一個實例對象,避免重復創建資源。

  • 延遲初始化:在程序運行過程中需要用到某個資源時,通過 sync.Once 動態地初始化該資源。

  • 只執行一次的操作:例如只需要執行一次的配置加載、數據清理等操作。

sync.Once 應用實例

單例模式

在單例模式中,我們需要確保一個結構體只被初始化一次。使用 sync.Once 可以輕松實現這一目標。

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 函數通過 once.Do() 確保 instance 只會被初始化一次。在并發環境下,多個協程同時調用 GetInstance 時,只有一個協程會執行 instance = &Singleton{},所有協程得到的實例 s 都是同一個。

延遲初始化

有時候希望在需要時才初始化某些資源。使用 sync.Once 可以實現這一目標。

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"])

   // 第二次需要,此時 config 已經被初始化過,無需再次初始化
   cfg2 := GetConfig()
   fmt.Println("c2: ", cfg2.config["c2"])
}

在這個示例中,定義了一個 Config 結構體,它包含一些設置信息。使用 sync.Once 來實現 GetConfig 函數,該函數在第一次調用時初始化 Config。這樣,我們可以在真正需要時才初始化 Config,從而避免不必要的開銷。

sync.Once 實現原理

type Once struct {
   // 表示是否執行了操作
   done uint32
   // 互斥鎖,確保多個協程訪問時,只能一個協程執行操作
   m    Mutex
}

func (o *Once) Do(f func()) {
   // 判斷 done 的值,如果是 0,說明 f 還沒有被執行過
   if atomic.LoadUint32(&o.done) == 0 {
      // 構建慢路徑(slow-path),以允許對 Do 方法的快路徑(fast-path)進行內聯
      o.doSlow(f)
   }
}

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

sync.Once 結構體包含兩個字段:donemudone 是一個 uint32 類型的變量,用于表示操作是否已經執行過;m 是一個互斥鎖,用于確保在多個協程訪問時,只有一個協程能執行操作。

sync.Once 結構體包含兩個方法:DodoSlowDo 方法是其核心方法,它接收一個函數參數 f。首先它會通過原子操作atomic.LoadUint32(保證并發安全) 檢查 done 的值,如果為 0,表示 f 函數沒有被執行過,然后執行 doSlow 方法。

doSlow 方法里,首先對互斥鎖 m 進行加鎖,確保在多個協程訪問時,只有一個協程能執行 f 函數。接著再次檢查 done 變量的值,如果 done 的值仍為 0,說明 f 函數沒有被執行過,此時執行 f 函數,最后通過原子操作 atomic.StoreUint32done 變量的值設置為 1。

為什么會封裝一個 doSlow 方法

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

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

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

  • 第一次檢查:在獲取鎖之前,先使用原子加載操作 atomic.LoadUint32 檢查 done 變量的值,如果 done 的值為 1,表示操作已執行,此時直接返回,不再執行 doSlow 方法。這一檢查可以避免不必要的鎖競爭。

  • 第二次檢查:獲取鎖之后,再次檢查 done 變量的值,這一檢查是為了確保在當前協程獲取鎖期間,其他協程沒有執行過 f 函數。如果 done 的值仍為 0,表示 f 函數沒有被執行過。

通過雙重檢查,可以在大多數情況下避免鎖競爭,提高性能。

加強的 sync.Once

sync.Once 提供的 Do 方法并沒有返回值,意味著如果我們傳入的函數如果發生 error 導致初始化失敗,后續調用 Do 方法也不會再初始化。為了避免這個問題,我們可以實現一個 類似 sync.Once 的并發原語。

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()
      // 只有沒有 error 的時候,才修改 done 的值
      if err == nil {
         atomic.StoreUint32(&o.done, 1)
      }
   }
   return err
}

上述代碼實現了一個加強的 Once 結構體。與標準的 sync.Once 不同,這個實現允許 Do 方法的函數參數返回一個 error。如果執行函數沒有返回 error,則修改 done 的值以表示函數已執行。這樣,在后續的調用中,只有在沒有發生 error 的情況下,才會跳過函數執行,避免初始化失敗。

sync.Once 的注意事項

死鎖

通過分析 sync.Once 的源碼,可以看到它包含一個名為 m 的互斥鎖字段。當我們在 Do 方法內部重復調用 Do 方法時,將會多次嘗試獲取相同的鎖。但是 mutex 互斥鎖并不支持可重入操作,因此這將導致死鎖現象。

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

初始化失敗

這里的初始化失敗指的是在調用 Do 方法之后,執行 f 函數的過程中發生 error,導致執行失敗,現有的 sync.Once 設計我們是無法感知到初始化的失敗的,為了解決這個問題,我們可以實現一個類似 sync.Once 的加強 once,前面的內容已經提供了具體實現。

關于“Golang并發利器sync.Once怎么使用”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“Golang并發利器sync.Once怎么使用”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

新竹市| 镇原县| 印江| 凉城县| 溧阳市| 柳林县| 藁城市| 阳春市| 渑池县| 高清| 泉州市| 梓潼县| 文安县| 庆元县| 锦屏县| 广平县| 紫云| 任丘市| 永济市| 天津市| 仙游县| 嘉荫县| 敦化市| 郧西县| 牙克石市| 尚志市| 永平县| 洞口县| 鞍山市| 仁寿县| 邵武市| 交城县| 清丰县| 通道| 池州市| 延安市| 洛宁县| 呼图壁县| 溧阳市| 西安市| 墨江|