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

go sync Once實(shí)現(xiàn)原理示例解析

 更新時(shí)間:2023年01月03日 11:18:12   作者:eleven26  
這篇文章主要為大家介紹了go sync Once實(shí)現(xiàn)原理示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

在很多情況下,我們可能需要控制某一段代碼只執(zhí)行一次,比如做某些初始化操作,如初始化數(shù)據(jù)庫連接等。 對(duì)于這種場(chǎng)景,go 為我們提供了 sync.Once 對(duì)象,它保證了某個(gè)動(dòng)作只被執(zhí)行一次。 當(dāng)然我們也是可以自己通過 Mutex 實(shí)現(xiàn) sync.Once 的功能,但是相比來說繁瑣了那么一點(diǎn), 因?yàn)槲覀儾粌H要自己去控制鎖,還要通過一個(gè)標(biāo)識(shí)來標(biāo)志是否已經(jīng)執(zhí)行過。

Once 的實(shí)現(xiàn)

Once 的實(shí)現(xiàn)非常簡(jiǎn)單,如下,就只有 20 來行代碼,但里面包含了 go 并發(fā)、同步的一些常見處理方法。

package sync
import (
   "sync/atomic"
)
type Once struct {
   done uint32
   m    Mutex
}
func (o *Once) Do(f func()) {
   if atomic.LoadUint32(&o.done) == 0 {
      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()
   }
}

簡(jiǎn)要說明:

  • done 字段指示了操作是否已執(zhí)行,也就是我們傳遞給 Do 的函數(shù)是否已經(jīng)被執(zhí)行。
  • Do 方法接收一個(gè)函數(shù)參數(shù),這個(gè)函數(shù)參數(shù)只會(huì)被執(zhí)行一次。
  • Once 內(nèi)部是通過 Mutex 來實(shí)現(xiàn)不同協(xié)程之間的同步的。

使用示例

在下面的例子中,once.Do(test) 被執(zhí)行了 3 次,但是最終 test 只被執(zhí)行了一次。

package sync
import (
   "fmt"
   "sync"
   "testing"
)
var once sync.Once
var a = 0
func test() {
   a++
}
func TestOnce(t *testing.T) {
   var wg sync.WaitGroup
   wg.Add(3)
   for i := 0; i < 3; i++ {
      go func() {
         // once.Do 會(huì)調(diào)用 3 次,但最終只會(huì)執(zhí)行一次
         once.Do(test)
         wg.Done()
      }()
   }
   wg.Wait()
   fmt.Println(a) // 1
}

Once 的一些工作機(jī)制

  • OnceDo 方法可以保證,在多個(gè) goroutine 同時(shí)執(zhí)行 Do 方法的時(shí)候, 在第一個(gè)搶占到 Do 執(zhí)行權(quán)的 goroutine 執(zhí)行返回之前,其他 goroutine 都會(huì)阻塞在 Once.Do 的調(diào)用上, 只有第一個(gè) Do 調(diào)用返回的時(shí)候,其他 goroutine 才可以繼續(xù)執(zhí)行下去,并且其他所有的 goroutine 不會(huì)再執(zhí)行傳遞給 Do 的函數(shù)。(如果是初始化的場(chǎng)景,這可以避免尚未初始化完成就執(zhí)行其他的操作)
  • 如果 Once.Do 發(fā)生 panic 的時(shí)候,傳遞給 Do 的函數(shù)依然被標(biāo)記為已完成。后續(xù)對(duì) Do 的調(diào)用也不會(huì)再執(zhí)行傳給 Do 的函數(shù)參數(shù)。
  • 我們不能簡(jiǎn)單地通過 atomic.CompareAndSwapUint32 來決定是否執(zhí)行 f(),因?yàn)樵诙鄠€(gè) goroutine 同時(shí)執(zhí)行的時(shí)候,它無法保證 f() 只被執(zhí)行一次。所以 Once 里面用了 Mutex,這樣就可以有效地保護(hù)臨界區(qū)。
// 錯(cuò)誤實(shí)現(xiàn),這不能保證 f 只被執(zhí)行一次
if atomic.CompareAndSwapUint32(&amp;o.done, 0, 1) {
    f()
}
  • Once.Do 的函數(shù)參數(shù)是沒有參數(shù)的,如果我們需要傳遞一些參數(shù),可以再對(duì) f 做一層包裹。
config.once.Do(func() { config.init(filename) })

Once 詳解

hotpath

這里說的 hotpath 指的是 Once 里的第一個(gè)字段 done

type Once struct {
   // hotpath
   done uint32
   m    Mutex
}

Once 結(jié)構(gòu)體的第一個(gè)字段是 done,這是因?yàn)?done 的訪問是遠(yuǎn)遠(yuǎn)大于 Once 中另外一個(gè)字段 m 的, 放在第一個(gè)字段中,編譯器就可以做一些優(yōu)化,因?yàn)榻Y(jié)構(gòu)體的地址其實(shí)就是結(jié)構(gòu)體第一個(gè)字段的地址, 這樣一來,在訪問 done 字段的時(shí)候,就不需要通過結(jié)構(gòu)體地址 + 偏移量的方式來訪問, 這在一定程度上提高了性能。

結(jié)構(gòu)體地址計(jì)算示例:

type person struct {
   name string
   age  int
}
func TestStruct(t *testing.T) {
   var p = person{
      name: "foo",
      age:  10,
   }
   // p 和 p.name 的地址相同
   // 0xc0000100a8, 0xc0000100a8
   fmt.Printf("%p, %p\n", &p, &p.name)
   // p.age 的地址
   // 0xc0000100b8
   fmt.Printf("%p\n", &p.age)
   // p.age 的地址也可以通過:結(jié)構(gòu)體地址 + age 字段偏移量 計(jì)算得出。
   // 0xc0000100b8
   fmt.Println(unsafe.Add(unsafe.Pointer(&p), unsafe.Offsetof(p.age)))
}

atomic.LoadUint32

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

Do 方法中,是通過 atomic.LoadUint32 的方式來判斷 done 是否等于 0 的, 這是因?yàn)?,如果直接使?done == 0 的方式的話,就有可能導(dǎo)致在 doSlow 里面對(duì) done 設(shè)置為 1 之后, 在 Do 方法里面無法正常觀測(cè)到。因此用了 atomic.LoadUint32。

而在 doSlow 里面是可以通過 done == 0 來判斷的,這是因?yàn)?doSlow 里面已經(jīng)通過 Mutex 保護(hù)起來了。 唯一設(shè)置 done = 1 的地方就在臨界區(qū)里面,所以 doSlow 里面通過 done == 0 來判斷是完全沒有問題的。

atomic.StoreUint32

func (o *Once) doSlow(f func()) {
   o.m.Lock()
   defer o.m.Unlock()
   if o.done == 0 {
      defer atomic.StoreUint32(&amp;o.done, 1)
      f()
   }
}

doSlow 方法中,設(shè)置 done 為 1 也是通過 atomic.StoreUint32 來設(shè)置的。 這樣就可以保證在設(shè)置了 done 為 1 之后,可以及時(shí)被其他 goroutine 看到。

Mutex

doSlow 的實(shí)現(xiàn)里面,最終還是要通過 Mutex 來保護(hù)臨界區(qū), 通過 Mutex 可以實(shí)現(xiàn) f 只被執(zhí)行一次,并且其他的 goroutine 都可以使用這一次 f 的執(zhí)行結(jié)果。 因?yàn)槠渌?goroutine 在第一次 f 調(diào)用未返回之前,都阻塞在獲取 Mutex 鎖的地方, 當(dāng)它們獲取到 Mutex 鎖的時(shí)候,得以繼續(xù)往下執(zhí)行,但這個(gè)時(shí)候 f 已經(jīng)執(zhí)行完畢了, 所以當(dāng)它們獲取到 Mutex 鎖之后其實(shí)什么也沒有干。

但是它們的阻塞狀態(tài)被解除了,可以繼續(xù)往下執(zhí)行。

總結(jié)

  • Once 保證了傳入的函數(shù)只會(huì)執(zhí)行一次,這常常用在一些初始化的場(chǎng)景、或者單例模式。
  • Once 可以保證所有對(duì) Do 的并發(fā)調(diào)用都是安全的,所有對(duì) Once.Do 調(diào)用之后的操作,一定會(huì)在第一次對(duì) f 調(diào)用之后執(zhí)行。(沒有獲取到 f 執(zhí)行權(quán)的 goroutine 會(huì)阻塞)
  • 即使 Once.Do 里面的 f 出現(xiàn)了 panic,后續(xù)也不會(huì)再次調(diào)用 f。

以上就是go sync Once實(shí)現(xiàn)原理示例解析的詳細(xì)內(nèi)容,更多關(guān)于go sync Once實(shí)現(xiàn)原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • golang NewRequest/gorequest實(shí)現(xiàn)http請(qǐng)求的示例代碼

    golang NewRequest/gorequest實(shí)現(xiàn)http請(qǐng)求的示例代碼

    本文主要介紹了golang NewRequest/gorequest實(shí)現(xiàn)http請(qǐng)求的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • golang?防緩存擊穿singleflight的實(shí)現(xiàn)

    golang?防緩存擊穿singleflight的實(shí)現(xiàn)

    本文主要介紹了golang?防緩存擊穿singleflight的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • Golang中Kafka的重復(fù)消費(fèi)和消息丟失問題的解決方案

    Golang中Kafka的重復(fù)消費(fèi)和消息丟失問題的解決方案

    在Kafka中無論是生產(chǎn)者發(fā)送消息到Kafka集群還是消費(fèi)者從Kafka集群中拉取消息,都是容易出現(xiàn)問題的,比較典型的就是消費(fèi)端的重復(fù)消費(fèi)問題、生產(chǎn)端和消費(fèi)端產(chǎn)生的消息丟失問題,下面將對(duì)這兩個(gè)問題出現(xiàn)的場(chǎng)景以及常見的解決方案進(jìn)行講解
    2023-08-08
  • Go打包靜態(tài)文件的兩種方式

    Go打包靜態(tài)文件的兩種方式

    使用 Go 開發(fā)應(yīng)用的時(shí)候,有時(shí)會(huì)遇到需要讀取靜態(tài)資源的情況,如果不打包處理這種靜態(tài)文件:發(fā)布單獨(dú)掛載這種靜態(tài)文件相對(duì)比較麻煩,就有人會(huì)想辦法把靜態(tài)資源文件打包進(jìn) Go 的程序文件中,下面介紹兩種打包方式:go-bindata、go:embed,需要的朋友可以參考下
    2024-04-04
  • 用go寫的五子棋預(yù)測(cè)算法的實(shí)現(xiàn)

    用go寫的五子棋預(yù)測(cè)算法的實(shí)現(xiàn)

    這篇文章主要介紹了用go寫的五子棋預(yù)測(cè)算法的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • 淺析Go語言中的緩沖區(qū)及其在fmt包中的應(yīng)用

    淺析Go語言中的緩沖區(qū)及其在fmt包中的應(yīng)用

    這篇文章主要為大家詳細(xì)介紹了Go語言中的緩沖區(qū)及其在fmt包中的應(yīng)用的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2024-01-01
  • Golang 語言map底層實(shí)現(xiàn)原理解析

    Golang 語言map底層實(shí)現(xiàn)原理解析

    這篇文章主要介紹了Golang 語言map底層實(shí)現(xiàn)原理解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-12-12
  • Go語言中常量定義方法實(shí)例分析

    Go語言中常量定義方法實(shí)例分析

    這篇文章主要介紹了Go語言中常量定義方法,以實(shí)例形式分析了Go語言中常量的定義及使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • Go語言題解LeetCode455分發(fā)餅干示例詳解

    Go語言題解LeetCode455分發(fā)餅干示例詳解

    這篇文章主要為大家介紹了Go語言題解LeetCode455分發(fā)餅干示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • 詳解在Go語言中如何實(shí)現(xiàn)枚舉類型

    詳解在Go語言中如何實(shí)現(xiàn)枚舉類型

    枚舉類型是一種常用的數(shù)據(jù)類型,用于表示一組有限的、預(yù)定義的、具名的常量值。而在?Go?語言里是沒有內(nèi)置枚舉類型的,所以本文將介紹如何實(shí)現(xiàn)?“枚舉類型”,需要的可以參考一下
    2023-04-04

最新評(píng)論

莆田市| 五家渠市| 西峡县| 临湘市| 滦平县| 曲水县| 秭归县| 竹山县| 屯门区| 宁都县| 天镇县| 赤峰市| 侯马市| 杭锦后旗| 庄河市| 沂水县| 庄浪县| 荔波县| 奉节县| 宜兰市| 西吉县| 九寨沟县| 安新县| 绥中县| 鱼台县| 康乐县| 石台县| 江山市| 曲周县| 泗阳县| 呈贡县| 永济市| 沧州市| 讷河市| 宝应县| 兰坪| 四子王旗| 新田县| 洛川县| 嘉定区| 昔阳县|