您好,登錄后才能下訂單哦!
這篇文章主要介紹了Golang并發(fā)利器sync.Once怎么使用的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇Golang并發(fā)利器sync.Once怎么使用文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。
sync.Once
是 Go
語(yǔ)言中的一種同步原語(yǔ),用于確保某個(gè)操作或函數(shù)在并發(fā)環(huán)境下只被執(zhí)行一次。它只有一個(gè)導(dǎo)出的方法,即 Do
,該方法接收一個(gè)函數(shù)參數(shù)。在 Do
方法被調(diào)用后,該函數(shù)將被執(zhí)行,而且只會(huì)執(zhí)行一次,即使在多個(gè)協(xié)程同時(shí)調(diào)用的情況下也是如此。
sync.Once 主要用于以下場(chǎng)景:
單例模式:確保全局只有一個(gè)實(shí)例對(duì)象,避免重復(fù)創(chuàng)建資源。
延遲初始化:在程序運(yùn)行過(guò)程中需要用到某個(gè)資源時(shí),通過(guò) sync.Once
動(dòng)態(tài)地初始化該資源。
只執(zhí)行一次的操作:例如只需要執(zhí)行一次的配置加載、數(shù)據(jù)清理等操作。
在單例模式中,我們需要確保一個(gè)結(jié)構(gòu)體只被初始化一次。使用 sync.Once
可以輕松實(shí)現(xiàn)這一目標(biāo)。
package main import ( "fmt" "sync" ) type Singleton struct{} var ( instance *Singleton once sync.Once ) func GetInstance() *Singleton { once.Do(func() { instance = &Singleton{} }) return instance } func main() { var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() s := GetInstance() fmt.Printf("Singleton instance address: %p\n", s) }() } wg.Wait() }
上述代碼中,GetInstance
函數(shù)通過(guò) once.Do()
確保 instance
只會(huì)被初始化一次。在并發(fā)環(huán)境下,多個(gè)協(xié)程同時(shí)調(diào)用 GetInstance
時(shí),只有一個(gè)協(xié)程會(huì)執(zhí)行 instance = &Singleton{}
,所有協(xié)程得到的實(shí)例 s
都是同一個(gè)。
有時(shí)候希望在需要時(shí)才初始化某些資源。使用 sync.Once
可以實(shí)現(xiàn)這一目標(biāo)。
package main import ( "fmt" "sync" ) type Config struct { config map[string]string } var ( config *Config once sync.Once ) func GetConfig() *Config { once.Do(func() { fmt.Println("init config...") config = &Config{ config: map[string]string{ "c1": "v1", "c2": "v2", }, } }) return config } func main() { // 第一次需要獲取配置信息,初始化 config cfg := GetConfig() fmt.Println("c1: ", cfg.config["c1"]) // 第二次需要,此時(shí) config 已經(jīng)被初始化過(guò),無(wú)需再次初始化 cfg2 := GetConfig() fmt.Println("c2: ", cfg2.config["c2"]) }
在這個(gè)示例中,定義了一個(gè) Config
結(jié)構(gòu)體,它包含一些設(shè)置信息。使用 sync.Once
來(lái)實(shí)現(xiàn) GetConfig
函數(shù),該函數(shù)在第一次調(diào)用時(shí)初始化 Config
。這樣,我們可以在真正需要時(shí)才初始化 Config
,從而避免不必要的開(kāi)銷(xiāo)。
type Once struct { // 表示是否執(zhí)行了操作 done uint32 // 互斥鎖,確保多個(gè)協(xié)程訪(fǎng)問(wèn)時(shí),只能一個(gè)協(xié)程執(zhí)行操作 m Mutex } func (o *Once) Do(f func()) { // 判斷 done 的值,如果是 0,說(shuō)明 f 還沒(méi)有被執(zhí)行過(guò) if atomic.LoadUint32(&o.done) == 0 { // 構(gòu)建慢路徑(slow-path),以允許對(duì) Do 方法的快路徑(fast-path)進(jìn)行內(nèi)聯(lián) o.doSlow(f) } } func (o *Once) doSlow(f func()) { // 加鎖 o.m.Lock() defer o.m.Unlock() // 雙重檢查,避免 f 已被執(zhí)行過(guò) if o.done == 0 { // 修改 done 的值 defer atomic.StoreUint32(&o.done, 1) // 執(zhí)行函數(shù) f() } }
sync.Once
結(jié)構(gòu)體包含兩個(gè)字段:done
和 mu
。done
是一個(gè) uint32
類(lèi)型的變量,用于表示操作是否已經(jīng)執(zhí)行過(guò);m
是一個(gè)互斥鎖,用于確保在多個(gè)協(xié)程訪(fǎng)問(wèn)時(shí),只有一個(gè)協(xié)程能執(zhí)行操作。
sync.Once
結(jié)構(gòu)體包含兩個(gè)方法:Do
和 doSlow
。Do
方法是其核心方法,它接收一個(gè)函數(shù)參數(shù) f
。首先它會(huì)通過(guò)原子操作atomic.LoadUint32
(保證并發(fā)安全) 檢查 done
的值,如果為 0,表示 f
函數(shù)沒(méi)有被執(zhí)行過(guò),然后執(zhí)行 doSlow
方法。
在 doSlow
方法里,首先對(duì)互斥鎖 m
進(jìn)行加鎖,確保在多個(gè)協(xié)程訪(fǎng)問(wèn)時(shí),只有一個(gè)協(xié)程能執(zhí)行 f
函數(shù)。接著再次檢查 done
變量的值,如果 done
的值仍為 0,說(shuō)明 f
函數(shù)沒(méi)有被執(zhí)行過(guò),此時(shí)執(zhí)行 f
函數(shù),最后通過(guò)原子操作 atomic.StoreUint32
將 done
變量的值設(shè)置為 1。
為什么會(huì)封裝一個(gè) doSlow 方法
doSlow
方法的存在主要是為了性能優(yōu)化。將慢路徑(slow-path
)代碼從 Do
方法中分離出來(lái),使得 Do
方法的快路徑(fast-path
)能夠被內(nèi)聯(lián)(inlined
),從而提高性能。
為什么會(huì)有雙重檢查(double check)的寫(xiě)法
從源碼可知,存在兩次對(duì) done
的值的判斷。
第一次檢查:在獲取鎖之前,先使用原子加載操作 atomic.LoadUint32
檢查 done
變量的值,如果 done
的值為 1,表示操作已執(zhí)行,此時(shí)直接返回,不再執(zhí)行 doSlow
方法。這一檢查可以避免不必要的鎖競(jìng)爭(zhēng)。
第二次檢查:獲取鎖之后,再次檢查 done
變量的值,這一檢查是為了確保在當(dāng)前協(xié)程獲取鎖期間,其他協(xié)程沒(méi)有執(zhí)行過(guò) f
函數(shù)。如果 done
的值仍為 0,表示 f
函數(shù)沒(méi)有被執(zhí)行過(guò)。
通過(guò)雙重檢查,可以在大多數(shù)情況下避免鎖競(jìng)爭(zhēng),提高性能。
sync.Once
提供的 Do
方法并沒(méi)有返回值,意味著如果我們傳入的函數(shù)如果發(fā)生 error
導(dǎo)致初始化失敗,后續(xù)調(diào)用 Do
方法也不會(huì)再初始化。為了避免這個(gè)問(wèn)題,我們可以實(shí)現(xiàn)一個(gè) 類(lèi)似 sync.Once
的并發(fā)原語(yǔ)。
package main import ( "sync" "sync/atomic" ) type Once struct { done uint32 m sync.Mutex } func (o *Once) Do(f func() error) error { if atomic.LoadUint32(&o.done) == 0 { return o.doSlow(f) } return nil } func (o *Once) doSlow(f func() error) error { o.m.Lock() defer o.m.Unlock() var err error if o.done == 0 { err = f() // 只有沒(méi)有 error 的時(shí)候,才修改 done 的值 if err == nil { atomic.StoreUint32(&o.done, 1) } } return err }
上述代碼實(shí)現(xiàn)了一個(gè)加強(qiáng)的 Once
結(jié)構(gòu)體。與標(biāo)準(zhǔn)的 sync.Once
不同,這個(gè)實(shí)現(xiàn)允許 Do
方法的函數(shù)參數(shù)返回一個(gè) error
。如果執(zhí)行函數(shù)沒(méi)有返回 error
,則修改 done
的值以表示函數(shù)已執(zhí)行。這樣,在后續(xù)的調(diào)用中,只有在沒(méi)有發(fā)生 error
的情況下,才會(huì)跳過(guò)函數(shù)執(zhí)行,避免初始化失敗。
通過(guò)分析 sync.Once
的源碼,可以看到它包含一個(gè)名為 m
的互斥鎖字段。當(dāng)我們?cè)?Do
方法內(nèi)部重復(fù)調(diào)用 Do
方法時(shí),將會(huì)多次嘗試獲取相同的鎖。但是 mutex
互斥鎖并不支持可重入操作,因此這將導(dǎo)致死鎖現(xiàn)象。
func main() { once := sync.Once{} once.Do(func() { once.Do(func() { fmt.Println("init...") }) }) }
這里的初始化失敗指的是在調(diào)用 Do
方法之后,執(zhí)行 f
函數(shù)的過(guò)程中發(fā)生 error
,導(dǎo)致執(zhí)行失敗,現(xiàn)有的 sync.Once
設(shè)計(jì)我們是無(wú)法感知到初始化的失敗的,為了解決這個(gè)問(wèn)題,我們可以實(shí)現(xiàn)一個(gè)類(lèi)似 sync.Once
的加強(qiáng) once
,前面的內(nèi)容已經(jīng)提供了具體實(shí)現(xiàn)。
關(guān)于“Golang并發(fā)利器sync.Once怎么使用”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“Golang并發(fā)利器sync.Once怎么使用”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。