SORCERERXW

Go sync.Once 原理

sync.Once 用于保证一段任务执行 exactly once。它的实现非常简单:

type Once struct {
  done uint32
  m    Mutex
}

func (o *Once) Do(f func()) {
  if atomic.LoadUint32(&o.done) == 0 {
    o.doSlow(f)
  }
}

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

可以看出 Once 的工作流程:

通过 atomic 来检查 done 判断是否已经被执行了

通过 Mutex 保证同一时刻只有一个协程进入执行

检查一遍 done 是否被改写

执行并更新 done 值

整个流程其实就是双检锁单例的实现,在锁的基础上引入了 double check,所以有下面几个点值得注意:

为什么要设置多次的检查?

观察一下 doSlow ,这本身就是一个完整且可以独立的工作的流程,但是它的问题在于它太慢了,每一次检查都需要竞争锁。所以为了提高性能,通过前置检查 atomic 来快速判断是否已经处理完成。

既然前面已经检查过 done 的值了,为什么在获取到锁执行前需要再检查一遍?

因为整个流程并非原子操作,在等待互斥锁的时候,前面的协程可能已经改写了 done 值,后面再放行进入的协程需要保证 done 没有被改写过。