wtysos11 / blogWiki

Use to store public paper and organize them.
17 stars 4 forks source link

从sync.Once启发golang并发编程入门 #169

Closed wtysos11 closed 3 years ago

wtysos11 commented 3 years ago

参考:

今天在看到42页作者自己实现的Once后,有点小问题。Once函数的作用是,对于多次Once的调用,只有一个能够执行。

Once在Go高级编程的实现:

type Once struct{
    m sync.Mutex
    done uint32
}
func (o *Once) Do (f func()){
    if atomic.LoadInt32(&o.done) == 1{
        return
    }
    o.m.Lock()
    defer o.m.Unlock()

    if o.done == 0{
        defer atomic.StoreUint32(&o.done,1)
        f()
    }
}

这里我在看的时候有两个问题,一个是为什么不用CAS( Compare And Swap),另一个是在if o.done == 0的时候可能会重复进入。

第二个实际上是看的时候不够专心,在判断之前专门加了个锁。通过模拟执行可以看出,两个进程假设完全同时的进入,必定会有一个被进程锁锁住。而在后续,因为设置了原子进程来判断状态位,与共享锁相比大幅降低了共享所需要的代价。

而第一个则是直接写在了官方文档内

// Do calls the function f if and only if Do is being called for the
// first time for this instance of Once. In other words, given
//  var once Once
// if once.Do(f) is called multiple times, only the first call will invoke f,
// even if f has a different value in each invocation. A new instance of
// Once is required for each function to execute.
//
// Do is intended for initialization that must be run exactly once. Since f
// is niladic, it may be necessary to use a function literal to capture the
// arguments to a function to be invoked by Do:
//  config.once.Do(func() { config.init(filename) })
//
// Because no call to Do returns until the one call to f returns, if f causes
// Do to be called, it will deadlock.
//
// If f panics, Do considers it to have returned; future calls of Do return
// without calling f.
//
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()
    }
}