在上一篇文章《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篇原创内容
公众号
👆 关注我,_一块儿在知识的海洋遨游_
转发,点赞,在看就是对我最大的鼓励
点击“阅读原文”查看参考文献等信息