问题
I have a question about sync.Once() in Go 1.12. The source code is below:
// Because no call to Do returns until the one call to f returns, if f causes
// Do to be called, it will deadlock.
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 1 {
return
}
// Slow-path.
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
Why not just use an uint32
variable, then do CAS on this variable. It seems to be more effective, and will not lead to deadlock.
The code like:
type Once uint32
func (o *Once) Do(f func()) {
if atomic.CompareAndSwapUint32((*uint32)(o), 0, 1) {
f()
}
}
回答1:
Once.Do() does not return until f()
has been executed once. Which means if multiple goroutines call Once.Do()
concurrently, f()
will be executed once of course, but all calls will wait until f()
completes (they will be blocked).
Your proposed solution does not have this very important property! Yours only guarantees that f()
will be executed only once, but if called from multiple goroutines concurrently, subsequent calls will return immediately, even if f()
is still running.
When we use sync.Once
, we rely on this behavior, we rely on f()
being completed after calling Once.Do()
, so we can use all variables that f()
initialized safely, without having a race condition.
来源:https://stackoverflow.com/questions/56423412/sync-once-implementation