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

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二次確認,傳入的函數(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?實現(xiàn)?MySQL?數(shù)據(jù)庫事務(wù)

    深入分析Go?實現(xiàn)?MySQL?數(shù)據(jù)庫事務(wù)

    本文深入分析了Go語言實現(xiàn)MySQL數(shù)據(jù)庫事務(wù)的原理和實現(xiàn)方式,包括事務(wù)的ACID特性、事務(wù)的隔離級別、事務(wù)的實現(xiàn)方式等。同時,本文還介紹了Go語言中的事務(wù)處理機制和相關(guān)的API函數(shù),以及如何使用Go語言實現(xiàn)MySQL數(shù)據(jù)庫事務(wù)。
    2023-06-06
  • 特殊字符的json序列化總結(jié)大全

    特殊字符的json序列化總結(jié)大全

    這篇文章主要給大家介紹了關(guān)于特殊字符的json序列化的相關(guān)資料,通過示例代碼分別給大家介紹了關(guān)于python 、 rust 、 java 和golang對特殊字符的json序列化操作,需要的朋友可以參考借鑒,下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-09-09
  • 基于golang時間轉(zhuǎn)換的問題

    基于golang時間轉(zhuǎn)換的問題

    下面小編就為大家?guī)硪黄趃olang時間轉(zhuǎn)換的問題。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • Go實現(xiàn)數(shù)據(jù)脫敏的方案設(shè)計

    Go實現(xiàn)數(shù)據(jù)脫敏的方案設(shè)計

    在一些常見的業(yè)務(wù)場景中可能涉及到用戶的手機號,銀行卡號等敏感數(shù)據(jù),對于這部分的數(shù)據(jù)經(jīng)常需要進行數(shù)據(jù)脫敏處理,就是將此部分數(shù)據(jù)隱私化,防止數(shù)據(jù)泄露,所以本文給大家介紹了Go實現(xiàn)數(shù)據(jù)脫敏的方案設(shè)計,需要的朋友可以參考下
    2024-05-05
  • 詳解Go語言中配置文件使用與日志配置

    詳解Go語言中配置文件使用與日志配置

    這篇文章主要為大家詳細講解一下Go語言中調(diào)整項目目錄結(jié)構(gòu)、增加配置文件使用和增加日志配置的方法,文中示例代碼講解詳細,需要的可以參考一下
    2022-06-06
  • golang值接收者和指針接收者的區(qū)別介紹

    golang值接收者和指針接收者的區(qū)別介紹

    這篇文章主要介紹了golang值接收者和指針接收者的區(qū)別,它和函數(shù)的區(qū)別在于方法有一個接收者,給一個函數(shù)添加一個接收者,那么它就變成了方法,接收者可以是值接收者,也可以是指針接收者,本文通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-08-08
  • Go語言操作Excel的實現(xiàn)示例

    Go語言操作Excel的實現(xiàn)示例

    excelize是一個功能豐富且易于使用的Go語言庫,它極大地簡化了Excel文件的讀寫操作,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-12-12
  • Go語言入門教程之基礎(chǔ)語法快速入門

    Go語言入門教程之基礎(chǔ)語法快速入門

    這篇文章主要介紹了Go語言入門教程之基礎(chǔ)語法快速入門,本文講解了值類型、變量、常量、循環(huán)、條件語句、條件枚舉等內(nèi)容,需要的朋友可以參考下
    2014-11-11
  • go語言使用中提示%!(NOVERB)的解決方案

    go語言使用中提示%!(NOVERB)的解決方案

    o語言的設(shè)計目標(biāo)是提供一種簡單易用的編程語言,同時保持高效性和可擴展性,它支持垃圾回收機制,具有強大的并發(fā)編程能力,可以輕松處理大規(guī)模的并發(fā)任務(wù),Go語言還擁有豐富的標(biāo)準(zhǔn)庫和活躍的開發(fā)社區(qū),使得開發(fā)者能夠快速構(gòu)建出高質(zhì)量的應(yīng)用程序,需要的朋友可以參考下
    2023-10-10
  • 詳解Opentelemetry Collector采集器

    詳解Opentelemetry Collector采集器

    這篇文章主要為大家介紹了Opentelemetry Collector神秘的采集器詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12

最新評論