最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

簡介

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

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

sync.Once 基本概念

什么是 sync.Once

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

sync.Once 的應用場景

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

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

sync.Once 應用實例

單例模式

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

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

延遲初始化

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

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 來實現(xiàn) GetConfig 函數(shù),該函數(shù)在第一次調用時初始化 Config。這樣,我們可以在真正需要時才初始化 Config,從而避免不必要的開銷。

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

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

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

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

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

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

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

為什么會封裝一個 doSlow 方法

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

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

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

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

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

加強的 sync.Once

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

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
}

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

sync.Once 的注意事項

死鎖

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

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

初始化失敗

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

小結

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

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

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

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

相關文章

  • 詳解Go語言如何熱重載和優(yōu)雅地關閉程序

    詳解Go語言如何熱重載和優(yōu)雅地關閉程序

    我們有時會因不同的目的去關閉服務,一種關閉服務是終止操作系統(tǒng),一種關閉服務是用來更新配置,本文就來和大家簡單講講這兩種方法的實現(xiàn)吧
    2023-07-07
  • 詳解golang各種類型是如何進行比較的

    詳解golang各種類型是如何進行比較的

    在日常開發(fā)中,比較操作是最常用的基本操作之一,可以用來判斷變量之間是否相等或者對應的大小關系,比較操作對于排序、查找和集合數(shù)據(jù)結構的實現(xiàn)至關重要,本文將深入解析golang各種類型是如何進行比較的,需要的朋友可以參考下
    2024-01-01
  • 理解Go流程控制與快樂路徑原則

    理解Go流程控制與快樂路徑原則

    這篇文章主要為大家介紹了Go流程控制與快樂路徑原則的原理解析,
    有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • go語言goto語句跳轉到指定的標簽實現(xiàn)方法

    go語言goto語句跳轉到指定的標簽實現(xiàn)方法

    這篇文章主要介紹了go語言goto語句跳轉到指定的標簽實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-05-05
  • 詳解Golang中strconv庫的用法

    詳解Golang中strconv庫的用法

    strconv包提供了字符串和基本數(shù)據(jù)類型之間的相互轉換功能,本文將帶大家深入了解Go語言標準庫中的strconv包,掌握其常用的函數(shù)和用法,希望對大家有所幫助
    2023-06-06
  • 使用Go語言實現(xiàn)跨域資源共享(CORS)設置

    使用Go語言實現(xiàn)跨域資源共享(CORS)設置

    在Web開發(fā)中,跨域資源共享(CORS)是一種重要的安全機制,它允許許多資源在一個網(wǎng)頁上被另一個來源的網(wǎng)頁所訪問,然而,出于安全考慮,瀏覽器默認禁止這種跨域訪問,為了解決這個問題,我們可以使用Go語言來設置CORS,需要的朋友可以參考下
    2024-06-06
  • Go語言同步等待組sync.WaitGroup結構體對象方法詳解

    Go語言同步等待組sync.WaitGroup結構體對象方法詳解

    這篇文章主要為大家介紹了Go語言同步等待組sync.WaitGroup結構體對象方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • 詳解Go語言如何解決map并發(fā)安全問題

    詳解Go語言如何解決map并發(fā)安全問題

    常說go語言是一門并發(fā)友好的語言,對于并發(fā)操作總會在編譯期完成安全檢查,所以這篇文章我們就來聊聊go語言是如何解決map這個數(shù)據(jù)結構的線程安全問題吧
    2024-04-04
  • 對Golang中的FORM相關字段理解

    對Golang中的FORM相關字段理解

    這篇文章主要介紹了對Golang中的FORM相關字段理解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • Go語言使用MongoDB數(shù)據(jù)庫詳細步驟

    Go語言使用MongoDB數(shù)據(jù)庫詳細步驟

    mongodb是一種高性能、開源、文檔型的nosql數(shù)據(jù)庫,被廣泛應用于web應用、大數(shù)據(jù)以及云計算領域,下面這篇文章主要給大家介紹了關于Go語言使用MongoDB數(shù)據(jù)庫的詳細步驟,需要的朋友可以參考下
    2024-05-05

最新評論

新巴尔虎左旗| 和平县| 寿宁县| 汶川县| 黄梅县| 苍南县| 宜章县| 二连浩特市| 思茅市| 江川县| 瑞昌市| 桃源县| 湟源县| 吉林市| 赣榆县| 五大连池市| 赣榆县| 屏边| 赤壁市| 孝昌县| 增城市| 贡觉县| 大荔县| 西盟| 遵义市| 蒙山县| 绍兴市| 区。| 基隆市| 南雄市| 泗阳县| 大足县| 嘉祥县| 西乌| 高陵县| 策勒县| 曲麻莱县| 涞源县| 登封市| 称多县| 耿马|