golang源码分析 sync.Once

crud-bird · 2020-04-10 15:32:12 · 1014 次点击
jan-bar
想要拥有,必定付出。

defer atomic.StoreUint32(&o.done, 1)是在Do返回之后执行,这个应该很有必要吧。我的1.14.1源码注释有说这个问题吧

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)
    }
}

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

你修改之后 o.done = 1是锁住的区域赋值的, if o.done==1没加锁读, 这里就有数据竞争的问题。除非用lock把你读的地方包裹下。好了,你现在得到的代码才是每次都能正确处理的实现。

对于这种代码,如果想优化,把锁的颗粒度变小,可以atomic把done的地方改改,最后发现,我c 这不就是标准库的写法吗。。。

#1

这里用的双检查锁结构,if o.done==1这一行并发读是没问题的,不存在你所谓的数据竞争。

#2