sync.Once如何实现线程安全

题目来源: 滴滴

答案:

  1. sync.Once源码分析
    sync.Once是通过一个对象实现的.

    type Once struct { done unit32 m Mutex }

    他们分别为标记是否已经执行过的标志(done),以及执行时所用的互斥锁(m)
    除了结构体外,sync.Once还包括了一个公开的方法Do:

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

    Once.Do方法的实现非常简单,通过atomic.LoadUint32获取Once实例的done属性值。
    若done值为0时,表示函数f未被调用过或正运行中且未结束,则将调用doSlow方法;
    若done值为1时,表示函数f已经调用且完成,则直接返回。
    这里使用了原子操作方法atomic.LoadUint32而不是直接将o.done进行比较,也是为了避免并发状态下错误地判断执行状态,产生不必要的锁操作带来的时间开销。

    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.doSlow方法的实现使用了传统的互斥锁Mutex操作,在执行时即调用o.m.Lock方法获得锁,然后再继续判断是否已经完成并调用f函数。
    可以看到,在获得锁后还需要对o.done的值进行一次判断,避免了f函数被重复调用。
    最后,在退出doSlow方法时还需要对获取的锁进行释放,若进入到f函数的调用则需要更改o.done属性值。