我对Go 1.12中的 sync.Once() 有疑问。源代码如下:
// 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()
}
}
为什么不只使用uint32
变量,而是对这个变量进行CAS。它似乎更有效,并且不会导致死锁。
类似的代码:
type Once uint32
func (o *Once) Do(f func()) {
if atomic.CompareAndSwapUint32((*uint32)(o), 0, 1) {
f()
}
}
答案 0 :(得分:10)
Once.Do()
仅在执行一次f()
之后才返回。这意味着,如果多个goroutine并发调用Once.Do()
,则f()
当然会执行一次,但是所有调用都会等到f()
完成(它们会被阻止)。
您提出的解决方案没有这个非常重要的属性!您只能保证f()
仅执行一次,但是即使同时从多个goroutine中调用,即使f()
仍在运行,后续调用也会立即返回。
使用sync.Once
时,我们依赖于此行为,我们依靠f()
在调用Once.Do()
之后完成,因此我们可以使用f()
安全初始化的所有变量,而没有比赛条件。