亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go并發(fā)編程之sync.Once使用實例詳解

 更新時間:2021年11月05日 09:06:52   作者:深度思維者  
sync.Once使用起來很簡單, 下面是一個簡單的使用案例,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧

一.序

單從庫名大概就能猜出其作用。sync.Once使用起來很簡單, 下面是一個簡單的使用案例

package main
 
import (
	"fmt"
	"sync"
)
 
func main() {
	var (
		once sync.Once
		wg   sync.WaitGroup
	)
 
	for i := 0; i < 10; i++ {
		wg.Add(1)
		// 這里要注意講i顯示的當參數(shù)傳入內部的匿名函數(shù)
		go func(i int) {
			defer wg.Done()
			// fmt.Println("once", i)
			once.Do(func() {
				fmt.Println("once", i)
			})
		}(i)
	}
 
	wg.Wait()
	fmt.Printf("over")
}

輸出:

❯ go run ./demo.go
once 9

測試如果不添加once.Do 這段代碼,則會輸出如下結果,并且每次執(zhí)行的輸出都不一樣。

once 9
once 0
once 3
once 6
once 4
once 1
once 5
once 2
once 7
once 8

從兩次輸出不同,我們可以得知 sync.Once的作用是:保證傳入的函數(shù)只執(zhí)行一次

二. 源碼分析

2.1結構體

Once的結構體如下

type Once struct {
    done uint32
    m    Mutex
}

每一個 sync.Once 結構體中都只包含一個用于標識代碼塊是否執(zhí)行過的 done 以及一個互斥鎖 sync.Mutex

2.2 接口

sync.Once.Dosync.Once 結構體對外唯一暴露的方法,該方法會接收一個入?yún)榭盏暮瘮?shù):

  • 如果傳入的函數(shù)已經(jīng)執(zhí)行過,會直接返回
  • 如果傳入的函數(shù)沒有執(zhí)行過, 會調用sync.Once.doSlow執(zhí)行傳入的參數(shù)
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)
	}
}

代碼注釋中特別給了一個說明: 很容易犯錯的一種實現(xiàn)

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

如果這么實現(xiàn)最大的問題是,如果并發(fā)調用,一個 goroutine 執(zhí)行,另外一個不會等正在執(zhí)行的這個成功之后返回,而是直接就返回了,這就不能保證傳入的方法一定會先執(zhí)行一次了

正確的實現(xiàn)方式

if atomic.LoadUint32(&o.done) == 0 {
    // Outlined slow-path to allow inlining of the fast-path.
    o.doSlow(f)
}

會先判斷 done 是否為 0,如果不為 0 說明還沒執(zhí)行過,就進入 doSlow

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

doSlow 當中使用了互斥鎖來保證只會執(zhí)行一次

具體的邏輯

  • 為當前Goroutine獲取互斥鎖
  • 執(zhí)行傳入的無入?yún)⒑瘮?shù);
  • 運行延遲函數(shù), 將成員變量done更新為1

三. 使用場景案例

3.1 單例模式

原子操作配合互斥鎖可以實現(xiàn)非常高效的單件模式?;コ怄i的代價比普通整數(shù)的原子讀寫高很多,在性能敏感的地方可以增加一個數(shù)字型的標志位,通過原子檢測標志位狀態(tài)降低互斥鎖的使用次數(shù)來提高性能。

type singleton struct {}
 
var (
    instance    *singleton
    initialized uint32
    mu          sync.Mutex
)
 
func Instance() *singleton {
    if atomic.LoadUint32(&initialized) == 1 {
        return instance
    }
 
    mu.Lock()
    defer mu.Unlock()
 
    if instance == nil {
        defer atomic.StoreUint32(&initialized, 1)
        instance = &singleton{}
    }
    return instance
}

而使用sync.Once能更簡單實現(xiàn)單例模式

type singleton struct {}
 
var (
    instance *singleton
    once     sync.Once
)
 
func Instance() *singleton {
    once.Do(func() {
        instance = &singleton{}
    })
    return instance
}

3.2 加載配置文件示例

延遲一個開銷很大的初始化操作到真正用到它的時候再執(zhí)行是一個很好的實踐。因為預先初始化一個變量(比如在init函數(shù)中完成初始化)會增加程序的啟動耗時,而且有可能實際執(zhí)行過程中這個變量沒有用上,那么這個初始化操作就不是必須要做的。我們來看一個例子:

var icons map[string]image.Image
 
func loadIcons() {
    icons = map[string]image.Image{
        "left":  loadIcon("left.png"),
        "up":    loadIcon("up.png"),
        "right": loadIcon("right.png"),
        "down":  loadIcon("down.png"),
    }
}
 
// Icon 被多個goroutine調用時不是并發(fā)安全的
// 因為map類型本就不是類型安全數(shù)據(jù)結構
func Icon(name string) image.Image {
    if icons == nil {
        loadIcons()
    }
    return icons[name]
}

多個goroutine并發(fā)調用Icon函數(shù)時不是并發(fā)安全的,編譯器和CPU可能會在保證每個goroutine都滿足串行一致的基礎上自由地重排訪問內存的順序。loadIcons函數(shù)可能會被重排為以下結果:

func loadIcons() {
    icons = make(map[string]image.Image)
    icons["left"] = loadIcon("left.png")
    icons["up"] = loadIcon("up.png")
    icons["right"] = loadIcon("right.png")
    icons["down"] = loadIcon("down.png")
}

在這種情況下就會出現(xiàn)即使判斷了icons不是nil也不意味著變量初始化完成了??紤]到這種情況,我們能想到的辦法就是添加互斥鎖,保證初始化icons的時候不會被其他的goroutine操作,但是這樣做又會引發(fā)性能問題。

可以使用sync.Once 改造代碼

var icons map[string]image.Image
 
var loadIconsOnce sync.Once
 
func loadIcons() {
    icons = map[string]image.Image{
        "left":  loadIcon("left.png"),
        "up":    loadIcon("up.png"),
        "right": loadIcon("right.png"),
        "down":  loadIcon("down.png"),
    }
}
 
// Icon 是并發(fā)安全的,并且保證了在代碼運行的時候才會加載配置
func Icon(name string) image.Image {
    loadIconsOnce.Do(loadIcons)
    return icons[name]
}

這樣設計就能保證初始化操作的時候是并發(fā)安全的并且初始化操作也不會被執(zhí)行多次。

四.總結

作為用于保證函數(shù)執(zhí)行次數(shù)的 sync.Once 結構體,它使用互斥鎖和 sync/atomic 包提供的方法實現(xiàn)了某個函數(shù)在程序運行期間只能執(zhí)行一次的語義。在使用該結構體時,我們也需要注意以下的問題:

  • sync.Once.Do 方法中傳入的函數(shù)只會被執(zhí)行一次,哪怕函數(shù)中發(fā)生了 panic;
  • 兩次調用 sync.Once.Do 方法傳入不同的函數(shù)只會執(zhí)行第一次調傳入的函數(shù);

五. 參考

到此這篇關于Go并發(fā)編程--sync.Once的文章就介紹到這了,更多相關Go并發(fā)編程內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 詳解Go-JWT-RESTful身份認證教程

    詳解Go-JWT-RESTful身份認證教程

    這篇文章主要介紹了詳解Go-JWT-RESTful身份認證教程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-09-09
  • golan參數(shù)校驗Validator

    golan參數(shù)校驗Validator

    這篇文章主要介紹了golan參數(shù)校驗Validator,validator包可以通過反射結構體struct的tag進行參數(shù)校驗,下面來看看文章的詳細介紹吧,需要的朋友也可以參考一下
    2021-12-12
  • go內存緩存BigCache實現(xiàn)BytesQueue源碼解讀

    go內存緩存BigCache實現(xiàn)BytesQueue源碼解讀

    這篇文章主要為大家介紹了go內存緩存BigCache實現(xiàn)BytesQueue源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • GoFrame框架garray對比PHP的array優(yōu)勢

    GoFrame框架garray對比PHP的array優(yōu)勢

    這篇文章主要為大家介紹了GoFrame框架garray對比PHP的array優(yōu)勢詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06
  • GoLang職責鏈模式代碼實現(xiàn)介紹

    GoLang職責鏈模式代碼實現(xiàn)介紹

    這篇文章主要介紹了GoLang職責鏈模式代碼實現(xiàn),職責鏈模式是一種常用的設計模式,可以提高代碼的靈活性與可維護性,職責鏈模式將請求和處理分離,可以讓請求在處理鏈中依次經(jīng)過多個處理者,直到找到能夠處理請求的處理者為止
    2023-05-05
  • GPT回答go語言和C語言map操作方法對比

    GPT回答go語言和C語言map操作方法對比

    這篇文章主要為大家介紹了GPT回答go語言和C語言map操作方法對比,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • Go編程中常見錯誤和不良實踐解析

    Go編程中常見錯誤和不良實踐解析

    這篇文章主要為大家介紹了Go編程中常見錯誤和不良實踐解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • Go語言無緩沖的通道的使用

    Go語言無緩沖的通道的使用

    Go語言中無緩沖的通道是指在接收前沒有能力保存任何值的通道,本文主要介紹了Go語言無緩沖的通道的使用,具有一定的參考價值,感興趣的可以了解一下
    2024-01-01
  • Golang?Compare?And?Swap算法詳細介紹

    Golang?Compare?And?Swap算法詳細介紹

    CAS算法是一種有名的無鎖算法。無鎖編程,即不使用鎖的情況下實現(xiàn)多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實現(xiàn)變量的同步,所以也叫非阻塞同步Non-blocking?Synchronization
    2022-10-10
  • 詳解Golang中創(chuàng)建error的方式總結與應用場景

    詳解Golang中創(chuàng)建error的方式總結與應用場景

    Golang中創(chuàng)建error的方式包括errors.New、fmt.Errorf、自定義實現(xiàn)了error接口的類型等,本文主要為大家介紹了這些方式的具體應用場景,需要的可以參考一下
    2023-07-07

最新評論