go sync包(六) Once

发布于:2024-07-01 ⋅ 阅读:(119) ⋅ 点赞:(0)

Once

sync.Once 可以保证在 Go 程序运行期间的某段代码只会执行一次。

func main() {
    o := sync.Once{}
    for i := 0; i < 10; i++ {
        o.Do(func() {
            fmt.Println("only once")
        })
    }
}
// Once is an object that will perform exactly one action.
//
// A Once must not be copied after first use.
//
// In the terminology of the Go memory model,
// the return from f “synchronizes before”
// the return from any call of once.Do(f).
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
}
  • done:done只会被修改一次,所以Once不能重用。
  • mutex:加锁保护,防并发。
    sync.Once.Dosync.Once 结构体对外唯一暴露的方法,该方法会接收一个入参为空的函数:
  • 如果传入的函数已经执行过,会直接返回。
  • 如果传入的函数没有执行过,会调用 sync.Once.doSlow 执行传入的函数。

Do

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.

   // 没有执行过,执行doslow函数
   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()) {
   // 加锁保护,确保一次只有一个gorountine执行
   o.m.Lock()
   defer o.m.Unlock()
   // 双重校验,确保done == 0的时候才会执行
   if o.done == 0 {
      // 这个defer会比解锁操作先完成
      defer atomic.StoreUint32(&o.done, 1)
      // 执行自定义函数
      f()
   }
}

小结

作为用于保证函数执行次数的 sync.Once 结构体,它使用互斥锁和 sync/atomic 包提供的方法实现了某个函数在程序运行期间只能执行一次的语义。
在使用该结构体时,我们也需要注意以下的问题:

  • sync.Once.Do 方法中传入的函数只会被执行一次,哪怕函数中发生了 panic
  • 两次调用 sync.Once.Do 方法传入不同的函数只会执行第一次调传入的函数。