在上一篇文章《Week03: Go 併發編程(七) 深刻理解 errgroup》當中看 errgourp
源碼的時候咱們發現最後返回 err
是經過 once 來只保證返回一個非 nil 的值的,本文就來看一下 Once 的使用與實現編程
once 的使用很簡單c#
`func main() {` `var (` `o sync.Once` `wg sync.WaitGroup` `)` `for i := 0; i < 10; i++ {` `wg.Add(1)` `go func(i int) {` `defer wg.Done()` `o.Do(func() {` `fmt.Println("once", i)` `})` `}(i)` `}` `wg.Wait()` `}`
輸出併發
`❯ go run ./main.go` `once 9`
`type Once struct {` `done uint32` `m Mutex` `}`
done 用於斷定函數是否執行,若是不爲 0 會直接返回函數
`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)` `}` `}`
看 go 的源碼真的能夠學到不少東西,在這裏還給出了很容易犯錯的一種實現源碼分析
`if atomic.CompareAndSwapUint32(&o.done, 0, 1) {` `f()` `}`
若是這麼實現最大的問題是,若是併發調用,一個 goroutine 執行,另一個不會等正在執行的這個成功以後返回,而是直接就返回了,這就不能保證傳入的方法必定會先執行一次了 因此回頭看官方的實現ui
`if atomic.LoadUint32(&o.done) == 0 {` `// Outlined slow-path to allow inlining of the fast-path.` `o.doSlow(f)` `}`
會先判斷 done 是否爲 0,若是不爲 0 說明還沒執行過,就進入 doSlow
atom
`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
當中使用了互斥鎖來保證只會執行一次spa
mohuishoucode
lailin.xyz 的博客帳號,關注但不限於 Go 開發,雲原生,K8s等圖片
36篇原創內容
公衆號
👆 關注我,_一塊兒在知識的海洋遨遊_
轉發,點贊,在看就是對我最大的鼓勵
點擊「閱讀原文」查看參考文獻等信息