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

golang中sync.Once只執(zhí)行一次的原理解析

 更新時間:2023年09月11日 10:30:29   作者:寫代碼的lorre  
在某些場景下,我們希望某個操作或者函數(shù)僅被執(zhí)行一次,比如單例模式的初始化,一些資源配置的加載等,golang中的sync.Once就實現(xiàn)了這個功能,本文就和大家一起解析sync.Once只執(zhí)行一次的原理,需要的朋友可以參考下

背景

在某些場景下,我們希望某個操作或者函數(shù)僅被執(zhí)行一次,比如單例模式的初始化,一些資源配置的加載等。

golang中的sync.Once就實現(xiàn)了這個功能,Once只對外提供一個Do方法,Do方法只接收一個函數(shù)參數(shù),它可以保證并發(fā)場景下,多次對Do方法進行調(diào)用時,參數(shù)對應(yīng)的函數(shù)只被執(zhí)行一次

快速入門

定義一個f1函數(shù),同時開啟10個并發(fā),通過Once提供的Do方法去執(zhí)行f1函數(shù),Once可以保證f1函數(shù)只被執(zhí)行一次

func TestOnce(t *testing.T) {
   once := sync.Once{}
   f1 := func() {
      fmt.Println("f1 func")
   }
   wg := sync.WaitGroup{}
   for i := 0; i < 10; i++ {
      wg.Add(1)
      go func() {
         defer wg.Done()
         once.Do(f1)
      }()
   }
   wg.Wait()
}

源碼分析

golang版本:1.18.2

源碼路徑:src/sync/Once.go

// Once is an object that will perform exactly one action.
//
// A Once must not be copied after first use.
type Once struct {
   // done indicates whether the action has been performed.
   // It is first in the struct because it is used in the hot path.
   // The hot path is inlined at every call site.
   // Placing done first allows more compact instructions on some architectures (amd64/386),
   // and fewer instructions (to calculate offset) on other architectures.
   done uint32
   m    Mutex
}
// Once只對外提供一個Do方法
func (o *Once) Do(f func()) {}
  • Once內(nèi)部有兩個字段:done和m
  • done用來表示傳入的函數(shù)是否已執(zhí)行完成,未執(zhí)行和執(zhí)行中時,done=0,執(zhí)行完成時,done=1
  • m互斥鎖,用來保證并發(fā)調(diào)用時,傳入的函數(shù)只被執(zhí)行一次

Do()

// Do calls the function f if and only if Do is being called for the
// first time for this instance of Once. In other words, given
//     var once Once
// if once.Do(f) is called multiple times, only the first call will invoke f,
// even if f has a different value in each invocation. A new instance of
// Once is required for each function to execute.
//
// Do is intended for initialization that must be run exactly once. Since f
// is niladic, it may be necessary to use a function literal to capture the
// arguments to a function to be invoked by Do:
//     config.once.Do(func() { config.init(filename) })
//
// Because no call to Do returns until the one call to f returns, if f causes
// Do to be called, it will deadlock.
//
// If f panics, Do considers it to have returned; future calls of Do return
// without calling f.
//
func (o *Once) Do(f func()) {
   // Note: Here is an incorrect implementation of Do:
   //
   // if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
   //    f()
   // }
   //
   // Do guarantees that when it returns, f has finished.
   // This implementation would not implement that guarantee:
   // given two simultaneous calls, the winner of the cas would
   // call f, and the second would return immediately, without
   // waiting for the first's call to f to complete.
   // This is why the slow path falls back to a mutex, and why
   // the atomic.StoreUint32 must be delayed until after f returns.
   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()
   if o.done == 0 {
      defer atomic.StoreUint32(&o.done, 1)
      f()
   }
}
  • 先通過atomic.LoadUint32(&o.done) == 0快速判斷,傳入的函數(shù)參數(shù),是否已經(jīng)執(zhí)行完成。若done=0,表示函數(shù)未執(zhí)行或正在執(zhí)行中;若done=1,表示函數(shù)已執(zhí)行完成,則快速返回
  • 通過m互斥鎖進行加鎖,保證并發(fā)安全
  • 通過o.done == 0二次確認(rèn),傳入的函數(shù)參數(shù)是否已經(jīng)被執(zhí)行。若此時done=0,因為上一步已經(jīng)通過m進行了加鎖,所以可以保證的是,傳入的函數(shù)還沒有被執(zhí)行,此時執(zhí)行函數(shù)后,把done改為1即可;若此時done!=0,則表示在等待鎖的期間,已經(jīng)有其他goroutine成功執(zhí)行了函數(shù),此時直接返回即可

注意點一:同一個Once不能復(fù)用

func TestOnce(t *testing.T) {
   once := sync.Once{}
   f1 := func() {
      fmt.Println("f1 func")
   }
   f2 := func() {
      fmt.Println("f2 func")
   }
   // f1執(zhí)行成功
   once.Do(f1)
   // f2不會執(zhí)行
   once.Do(f2)
}

定義f1和f2兩個函數(shù),通過同一個Once來執(zhí)行時,只能保證f1函數(shù)被執(zhí)行一次

Once.Do保證的是第一個傳入的函數(shù)參數(shù)只被執(zhí)行一次,不是保證每一個傳入的函數(shù)參數(shù)都只被執(zhí)行一次,同一個Once不能復(fù)用,如果想要f1和f2都只被執(zhí)行一次,可以初始化兩個Once

注意點二:錯誤實現(xiàn)

if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
   f()
}

為什么通過CAS來實現(xiàn)是錯誤的?

因為CAS只能保證函數(shù)被執(zhí)行一次,但是不能保證f()還在執(zhí)行時,其他goroutine等待其執(zhí)行完成后再返回。這個很重要,當(dāng)我們傳入的函數(shù)是比較耗時的操作,比如和db建立連接等,就必須等待函數(shù)執(zhí)行完成再返回,不然就會出現(xiàn)一些未知的操作

注意點三:atomic.LoadUint32(&o.done) == 0和atomic.StoreUint32(&o.done, 1)

為什么使用atomic.LoadUint32(&o.done) == 0來判斷,而不是使用o.done == 0來判斷

為了防止發(fā)生數(shù)據(jù)競爭,使用o.done == 0來判斷,會發(fā)生數(shù)據(jù)競爭(Data Race)

數(shù)據(jù)競爭問題是指至少存在兩個線程/協(xié)程去讀寫某個共享內(nèi)存,其中至少一個線程/協(xié)程對其共享內(nèi)存進行寫操作

多個線程/協(xié)程同時對共享內(nèi)存的進行寫操作時,在寫的過程中,其他的線程/協(xié)程讀到數(shù)據(jù)是內(nèi)存數(shù)據(jù)中非正確預(yù)期的

驗證數(shù)據(jù)競爭問題:

package main
import (
   "fmt"
   "sync"
)
func main() {
   once := Once{}
   var wg sync.WaitGroup
   wg.Add(2)
   go func() {
      once.Do(print)
      wg.Done()
   }()
   go func() {
      once.Do(print)
      wg.Done()
   }()
   wg.Wait()
   fmt.Println("end")
}
func print() {
   fmt.Println("qqq")
}
type Once struct {
   done uint32
   m    sync.Mutex
}
func (o *Once) Do(f func()) {
   // 原來:atomic.LoadUint32(&o.done) == 0
   if o.done == 0 {
      o.doSlow(f)
   }
}
func (o *Once) doSlow(f func()) {
   o.m.Lock()
   defer o.m.Unlock()
   if o.done == 0 {
      // 原來:atomic.StoreUint32(&o.done, 1)
      defer func() {
         o.done = 1
      }()
      f()
   }
}

執(zhí)行命令:

 go run -race main.go

執(zhí)行結(jié)果:

qqq
==================
WARNING: DATA RACE
Write at 0x00c0000bc014 by goroutine 7:
  main.(*Once).doSlow.func1()
      /Users/cr/Documents/golang/src/ahut.com/go/demo/main.go:44 +0x32
  runtime.deferreturn()
      /usr/local/go/src/runtime/panic.go:436 +0x32
  main.(*Once).Do()
      /Users/cr/Documents/golang/src/ahut.com/go/demo/main.go:35 +0x52
  main.main.func1()
      /Users/cr/Documents/golang/src/ahut.com/go/demo/main.go:13 +0x37
Previous read at 0x00c0000bc014 by goroutine 8:
  main.(*Once).Do()
      /Users/cr/Documents/golang/src/ahut.com/go/demo/main.go:34 +0x3c
  main.main.func2()
      /Users/cr/Documents/golang/src/ahut.com/go/demo/main.go:17 +0x37
Goroutine 7 (running) created at:
  main.main()
      /Users/cr/Documents/golang/src/ahut.com/go/demo/main.go:12 +0x136
Goroutine 8 (running) created at:
  main.main()
      /Users/cr/Documents/golang/src/ahut.com/go/demo/main.go:16 +0x1da
==================
end
Found 1 data race(s)
exit status 66

以上就是golang中sync.Once只執(zhí)行一次的原理解析的詳細內(nèi)容,更多關(guān)于golang sync.Once執(zhí)行一次的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 深入探討Go語言中的預(yù)防性接口為什么是不必要的

    深入探討Go語言中的預(yù)防性接口為什么是不必要的

    在Go語言中,有一種從其他語言帶來的常見模式:預(yù)防性接口,雖然這種模式在?Java?等語言中很有價值,但在Go中往往會成為反模式,本文我們就來深入探討一下原因
    2025-01-01
  • 淺談go中defer的一個隱藏功能

    淺談go中defer的一個隱藏功能

    這篇文章主要介紹了淺談go中defer的一個隱藏功能,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • 一文帶大家了解Go語言中的內(nèi)聯(lián)優(yōu)化

    一文帶大家了解Go語言中的內(nèi)聯(lián)優(yōu)化

    內(nèi)聯(lián)優(yōu)化是一種常見的編譯器優(yōu)化策略,通俗來講,就是把函數(shù)在它被調(diào)用的地方展開,這樣可以減少函數(shù)調(diào)用所帶來的開銷,本文主要為大家介紹了Go中內(nèi)聯(lián)優(yōu)化的具體使用,需要的可以參考下
    2023-05-05
  • Golang 實現(xiàn) Redis系列(六)如何實現(xiàn) pipeline 模式的 redis 客戶端

    Golang 實現(xiàn) Redis系列(六)如何實現(xiàn) pipeline 模式的 redis 客戶端

    pipeline 模式的 redis 客戶端需要有兩個后臺協(xié)程負(fù)責(zé) tcp 通信,調(diào)用方通過 channel 向后臺協(xié)程發(fā)送指令,并阻塞等待直到收到響應(yīng),本文是使用 golang 實現(xiàn) redis 系列的第六篇, 將介紹如何實現(xiàn)一個 Pipeline 模式的 Redis 客戶端。
    2021-07-07
  • go開源Hugo站點構(gòu)建三步曲之集結(jié)渲染

    go開源Hugo站點構(gòu)建三步曲之集結(jié)渲染

    這篇文章主要為大家介紹了go開源Hugo站點構(gòu)建三步曲之集結(jié)渲染詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • 解決go mod私有倉庫拉取的問題

    解決go mod私有倉庫拉取的問題

    這篇文章主要介紹了解決go mod私有倉庫拉取的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • golang中的struct操作

    golang中的struct操作

    結(jié)構(gòu)體是一種聚合的數(shù)據(jù)類型,是由零個或多個任意類型的值聚合成的實體,每個值稱為結(jié)構(gòu)體的成員。下面介紹下golang中的struct,感興趣的朋友一起看看吧
    2021-11-11
  • Go語言中最便捷的http請求包resty的使用詳解

    Go語言中最便捷的http請求包resty的使用詳解

    go語言雖然自身就有net/http包,但是說實話用起來沒那么好用,resty包是go語言中一個非常受歡迎的http請求處理包,下面我們一起來學(xué)習(xí)一下resty的具體使用吧
    2025-03-03
  • go語言實現(xiàn)http服務(wù)端與客戶端的例子

    go語言實現(xiàn)http服務(wù)端與客戶端的例子

    今天小編就為大家分享一篇go語言實現(xiàn)http服務(wù)端與客戶端的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-08-08
  • 解決電腦用GoLand太卡將VsCode定制成Go IDE步驟過程

    解決電腦用GoLand太卡將VsCode定制成Go IDE步驟過程

    這篇文章主要為大家介紹了解決電腦用GoLand太卡,將VsCode定制成Go IDE步驟過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-11-11

最新評論

正阳县| 平和县| 大城县| 屏东县| 额济纳旗| 霍城县| 建宁县| 蓬溪县| 双鸭山市| 高平市| 巨野县| 丁青县| 土默特右旗| 益阳市| 天台县| 宜良县| 黑山县| 安吉县| 楚雄市| 体育| 阿鲁科尔沁旗| 库尔勒市| 从化市| 陇川县| 平利县| 临西县| 通化县| 麻江县| 绥江县| 监利县| 大洼县| 新安县| 平湖市| 宜城市| 和硕县| 新乡市| 临潭县| 西林县| 潜山县| 梁平县| 定南县|