欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

go并發(fā)利器sync.Once使用示例詳解

 更新時間:2023年03月14日 09:30:32   作者:starrySky  
這篇文章主要為大家介紹了go并發(fā)利器sync.Once使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

1. 簡介

本文主要介紹 Go 語言中的 Once 并發(fā)原語,包括 Once 的基本使用方法、原理和注意事項,從而對 Once 的使用有基本的了解。

2. 基本使用

2.1 基本定義

sync.Once是Go語言中的一個并發(fā)原語,用于保證某個函數(shù)只被執(zhí)行一次。Once類型有一個Do方法,該方法接收一個函數(shù)作為參數(shù),并在第一次調(diào)用時執(zhí)行該函數(shù)。如果Do方法被多次調(diào)用,只有第一次調(diào)用會執(zhí)行傳入的函數(shù)。

2.2 使用方式

使用sync.Once非常簡單,只需要創(chuàng)建一個Once類型的變量,然后在需要保證函數(shù)只被執(zhí)行一次的地方調(diào)用其Do方法即可。下面是一個簡單的例子:

var once sync.Once
func initOperation() {
    // 這里執(zhí)行一些初始化操作,只會被執(zhí)行一次
}
func main() {
    // 在程序啟動時執(zhí)行initOperation函數(shù),保證初始化只被執(zhí)行一次
    once.Do(initOperation)   
    // 后續(xù)代碼
}

2.3 使用例子

下面是一個簡單使用sync.Once的例子,其中我們使用sync.Once來保證全局變量config只會被初始化一次:

package main
import (
    "fmt"
    "sync"
)
var (
    config map[string]string
    once   sync.Once
)
func loadConfig() {
    // 模擬從配置文件中加載配置信息
    fmt.Println("load config...")
    config = make(map[string]string)
    config["host"] = "127.0.0.1"
    config["port"] = "8080"
}
func GetConfig() map[string]string {
    once.Do(loadConfig)
    return config
}
func main() {
    // 第一次調(diào)用GetConfig會執(zhí)行l(wèi)oadConfig函數(shù),初始化config變量
    fmt.Println(GetConfig())
    // 第二次調(diào)用GetConfig不會執(zhí)行l(wèi)oadConfig函數(shù),直接返回已初始化的config變量
    fmt.Println(GetConfig())
}

在這個例子中,我們定義了一個全局變量config和一個sync.Once類型的變量once。在GetConfig函數(shù)中,我們通過調(diào)用once.Do方法來保證loadConfig函數(shù)只會被執(zhí)行一次,從而保證config變量只會被初始化一次。 運行上面的程序,輸出如下:

load config...
map[host:127.0.0.1 port:8080]
map[host:127.0.0.1 port:8080]

可以看到,GetConfig函數(shù)在第一次調(diào)用時執(zhí)行了loadConfig函數(shù),初始化了config變量。在第二次調(diào)用時,loadConfig函數(shù)不會被執(zhí)行,直接返回已經(jīng)初始化的config變量。

3. 原理

下面是sync.Once的具體實現(xiàn)如下:

type Once struct {
   done uint32
   m    Mutex
}
func (o *Once) Do(f func()) {    
    // 判斷done標記位是否為0
   if atomic.LoadUint32(&o.done) == 0 {
      // Outlined slow-path to allow inlining of the fast-path.
      o.doSlow(f)
   }
}
func (o *Once) doSlow(f func()) {
   // 加鎖
   o.m.Lock()
   defer o.m.Unlock()
   // 執(zhí)行雙重檢查,再次判斷函數(shù)是否已經(jīng)執(zhí)行
   if o.done == 0 {
      defer atomic.StoreUint32(&o.done, 1)
      f()
   }
}

sync.Once的實現(xiàn)原理比較簡單,主要依賴于一個done標志位和一個互斥鎖。當Do方法被第一次調(diào)用時,會先原子地讀取done標志位,如果該標志位為0,說明函數(shù)還沒有被執(zhí)行過,此時會加鎖并執(zhí)行傳入的函數(shù),并將done標志位置為1,然后釋放鎖。如果標志位為1,說明函數(shù)已經(jīng)被執(zhí)行過了,直接返回。

4. 使用注意事項

4.1 不能將sync.Once作為函數(shù)局部變量

下面是一個簡單的例子,說明將 sync.Once 作為局部變量會導(dǎo)致的問題:

var config map[string]string
func initConfig() {
    fmt.Println("initConfig called")
    config["1"] = "hello world"
}
func getConfig() map[string]string{
    var once sync.Once
    once.Do(initCount)
    fmt.Println("getConfig called")
}
func main() {
    for i := 0; i < 10; i++ {
        go getConfig()
    }
    time.Sleep(time.Second)
}

這里初始化函數(shù)會被多次調(diào)用,這與initConfig 方法只會執(zhí)行一次的預(yù)期不符。這是因為將 sync.Once 作為局部變量時,每次調(diào)用函數(shù)都會創(chuàng)建新的 sync.Once 實例,每個 sync.Once 實例都有自己的 done 標志,多個實例之間無法共享狀態(tài)。導(dǎo)致初始化函數(shù)會被多次調(diào)用。

如果將 sync.Once 作為全局變量或包級別變量,就可以避免這個問題。所以基于此,不能定義sync.Once 作為函數(shù)局部變量來使用。

4.2 不能在once.Do中再次調(diào)用once.Do

下面舉一個在once.Do方法中再次調(diào)用once.Do 方法的例子:

package main
import (
"fmt"
"sync"
)
func main() {
   var once sync.Once
   var onceBody func()
   onceBody = func() {
      fmt.Println("Only once")
      once.Do(onceBody) // 再次調(diào)用once.Do方法
   }
   // 執(zhí)行once.Do方法
   once.Do(onceBody)
   fmt.Println("done")
}

在上述代碼中,當once.Do(onceBody)第一次執(zhí)行時,會輸出"Only once",然后在執(zhí)行once.Do(onceBody)時會發(fā)生死鎖,程序無法繼續(xù)執(zhí)行下去。

這是因為once.Do()方法在執(zhí)行過程中會獲取互斥鎖,在方法內(nèi)再次調(diào)用once.Do()方法,那么就會在獲取互斥鎖時出現(xiàn)死鎖。

因此,我們不能在once.Do方法中再次調(diào)用once.Do方法。

4.3 需要對傳入的函數(shù)進行錯誤處理

4.3.1 基本說明

一般情況下,如果傳入的函數(shù)不會出現(xiàn)錯誤,可以不進行錯誤處理。但是,如果傳入的函數(shù)可能出現(xiàn)錯誤,就必須對其進行錯誤處理,否則可能會導(dǎo)致程序崩潰或出現(xiàn)不可預(yù)料的錯誤。

因此,在編寫傳入Once的Do方法的函數(shù)時,需要考慮到錯誤處理問題,保證程序的健壯性和穩(wěn)定性。

4.3.2 未錯誤處理導(dǎo)致的問題

下面舉一個傳入的函數(shù)可能出現(xiàn)錯誤,但是沒有對其進行錯誤處理的例子:

import (
   "fmt"
   "net"
   "sync"
)
var (
   initialized bool
   connection  net.Conn
   initOnce    sync.Once
)
func initConnection() {
   connection, _ = net.Dial("tcp", "err_address")
}
func getConnection() net.Conn {
   initOnce.Do(initConnection)
   return connection
}
func main() {
   conn := getConnection()
   fmt.Println(conn)
   conn.Close()
}

在上面例子中,其中initConnection 為傳入的函數(shù),用于建立TCP網(wǎng)絡(luò)連接,但是在sync.Once中執(zhí)行該函數(shù)時,是有可能返回錯誤的,而這里并沒有進行錯誤處理,直接忽略掉錯誤。此時調(diào)用getConnection 方法,如果initConnection報錯的話,獲取連接時會返回空連接,后續(xù)調(diào)用將會出現(xiàn)空指針異常。因此,如果傳入sync.Once當中的函數(shù)可能發(fā)生異常,此時應(yīng)該需要對其進行處理。

4.3.3 處理方式

  • 4.3.3.1 panic退出執(zhí)行

應(yīng)用程序第一次啟動時,此時調(diào)用sync.Once來初始化一些資源,此時發(fā)生錯誤,同時初始化的資源是必須初始化的,可以考慮在出現(xiàn)錯誤的情況下,使用panic將程序退出,避免程序繼續(xù)執(zhí)行導(dǎo)致更大的問題。具體代碼示例如下:

import (
   "fmt"
   "net"
   "sync"
)
var (
   connection  net.Conn
   initOnce    sync.Once
)
func initConnection() {
   // 嘗試建立連接
   connection, err = net.Dial("tcp", "err_address")
    if err != nil {
       panic("net.Dial error")
    }
}
func getConnection() net.Conn {
   initOnce.Do(initConnection)
   return connection
}

如上,當initConnection方法報錯后,此時我們直接panic,退出整個程序的執(zhí)行。

  • 4.3.3.2 修改sync.Once實現(xiàn),Do函數(shù)的語意修改為只成功執(zhí)行一次

在程序運行過程中,可以選擇記錄下日志或者返回錯誤碼,而不需要中斷程序的執(zhí)行。然后下次調(diào)用時再執(zhí)行初始化的邏輯。這里需要對sync.Once進行改造,原本sync.Once中Do函數(shù)的實現(xiàn)為執(zhí)行一次,這里將其修改為只成功執(zhí)行一次。具體使用方式需要根據(jù)具體業(yè)務(wù)場景來決定。下面是其中一個實現(xiàn):

type MyOnce struct {
   done int32
   m    sync.Mutex
}
func (o *MyOnce) Do(f func() error) {
   if atomic.LoadInt32(&o.done) == 0 {
      o.doSlow(f)
   }
}
func (o *MyOnce) doSlow(f func() error) {
   o.m.Lock()
   defer o.m.Unlock()
   if o.done == 0 {
      // 只有在函數(shù)調(diào)用不返回err時,才會設(shè)置done
      if err := f(); err == nil {
         atomic.StoreInt32(&o.done, 1)
      }
   }
}

上述代碼中,增加了一個錯誤處理邏輯。當 f() 函數(shù)返回錯誤時,不會將 done 標記位置為 1,以便下次調(diào)用時可以重新執(zhí)行初始化邏輯。

需要注意的是,這種方式雖然可以解決初始化失敗后的問題,但可能會導(dǎo)致初始化函數(shù)被多次調(diào)用。因此,在編寫f() 函數(shù)時,需要考慮到這個問題,以避免出現(xiàn)不可預(yù)期的結(jié)果。

下面是一個簡單的例子,使用我們重新實現(xiàn)的Once,展示第一次初始化失敗時,第二次調(diào)用會重新執(zhí)行初始化邏輯,并成功初始化:

var (
   hasCall bool
   conn    net.Conn
   m       MyOnce
)
func initConn() (net.Conn, error) {
   fmt.Println("initConn...")
   // 第一次執(zhí)行,直接返回錯誤
   if !hasCall {
      return nil, errors.New("init error")
   }
   // 第二次執(zhí)行,初始化成功,這里默認其成功
   conn, _ = net.Dial("tcp", "baidu.com:80")
   return conn, nil
}
func GetConn() (net.Conn, error) {
   m.Do(func() error {
      var err error
      conn, err = initConn()
      if err != nil {
         return err
      }
      return nil
   })
   // 第一次執(zhí)行之后,將hasCall設(shè)置為true,讓其執(zhí)行初始化邏輯
   hasCall = true
   return conn, nil
}
func main() {
   // 第一次執(zhí)行初始化邏輯,失敗
   GetConn()
   // 第二次執(zhí)行初始化邏輯,還是會執(zhí)行,此次執(zhí)行成功
   GetConn()
   // 第二次執(zhí)行成功,第三次調(diào)用,將不會執(zhí)行初始化邏輯
   GetConn()
}

在這個例子中,第一次調(diào)用Do方法初始化失敗了,done標記位被設(shè)置為0。在第二次調(diào)用Do方法時,由于done標記位為0,會重新執(zhí)行初始化邏輯,這次初始化成功了,done標記位被設(shè)置為1。第三次調(diào)用,由于之前Do方法已經(jīng)執(zhí)行成功了,不會再執(zhí)行初始化邏輯。

5. 總結(jié)

本文旨在介紹Go語言中的Once并發(fā)原語,包括其基本使用、原理和注意事項,讓大家對Once有一個基本的了解。

首先,我們通過示例演示了Once的基本使用方法,并強調(diào)了其僅會執(zhí)行一次的特性。然后,我們解釋了Once僅執(zhí)行一次的原因,使讀者更好地理解Once的工作原理。最后,我們指出了使用Once時的一些注意事項,以避免誤用。

總之,本文全面地介紹了Go語言中的Once并發(fā)原語,使讀者能夠更好地理解和應(yīng)用它。

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

相關(guān)文章

  • golang獲取變量或?qū)ο箢愋偷膸追N方式總結(jié)

    golang獲取變量或?qū)ο箢愋偷膸追N方式總結(jié)

    在golang中并沒有提供內(nèi)置函數(shù)來獲取變量的類型,但是通過一定的方式也可以獲取,下面這篇文章主要給大家介紹了關(guān)于golang獲取變量或?qū)ο箢愋偷膸追N方式,需要的朋友可以參考下
    2022-12-12
  • go語言讀取json并下載高清妹子圖片

    go語言讀取json并下載高清妹子圖片

    前面我們介紹了使用python下載高清妹子圖,作為程序猿,我們當然不能只會一種語言,今天我們就來使用go語言來讀取API來下載妹子圖吧,有需要的宅男們可以參考下。
    2015-03-03
  • 詳解Golang互斥鎖內(nèi)部實現(xiàn)

    詳解Golang互斥鎖內(nèi)部實現(xiàn)

    本篇文章主要介紹了詳解Golang互斥鎖內(nèi)部實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • Go語言實現(xiàn)順序存儲的線性表實例

    Go語言實現(xiàn)順序存儲的線性表實例

    這篇文章主要介紹了Go語言實現(xiàn)順序存儲的線性表的方法,實例分析了Go語言實現(xiàn)線性表的定義、插入、刪除元素等的使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-03-03
  • go內(nèi)存緩存如何new一個bigcache對象示例詳解

    go內(nèi)存緩存如何new一個bigcache對象示例詳解

    這篇文章主要為大家介紹了go內(nèi)存緩存如何new一個bigcache對象示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • GO利用channel協(xié)調(diào)協(xié)程的實現(xiàn)

    GO利用channel協(xié)調(diào)協(xié)程的實現(xiàn)

    本文主要介紹了GO利用channel協(xié)調(diào)協(xié)程的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • Golang使用bcrypt實現(xiàn)密碼加密和校驗的操作代碼

    Golang使用bcrypt實現(xiàn)密碼加密和校驗的操作代碼

    bcrypt可以用于數(shù)據(jù)庫中的用戶密碼保存,相比md5而言更加的安全可靠,這篇文章主要介紹了Golang使用bcrypt實現(xiàn)密碼加密和校驗的操作代碼,需要的朋友可以參考下
    2024-05-05
  • 詳解Go語言中Validator庫的使用方法和用途

    詳解Go語言中Validator庫的使用方法和用途

    github.com/go-playground/validator 是一個 Go 語言的庫,用于對結(jié)構(gòu)體字段進行驗證,它提供了一種簡單而靈活的方式來定義驗證規(guī)則,在這篇文章中,我們將從一個簡單的問題出發(fā),帶你了解 Validator 庫的用途,也會介紹Validator 的基本使用
    2023-09-09
  • 深入解析Go語言的io.ioutil標準庫使用

    深入解析Go語言的io.ioutil標準庫使用

    這篇文章主要介紹了Go語言的io.ioutil標準庫使用,是Golang入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下
    2015-10-10
  • golang 40行代碼實現(xiàn)通用協(xié)程池

    golang 40行代碼實現(xiàn)通用協(xié)程池

    golang協(xié)程機制很方便的解決了并發(fā)編程的問題,但是協(xié)程并不是沒有開銷的,所以也需要適當限制一下數(shù)量。這篇文章主要介紹了golang 40行代碼實現(xiàn)通用協(xié)程池,需要的朋友可以參考下
    2018-08-08

最新評論