# 源码分析 sync.Once
## 源码
```
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()
}
}
```
## 疑问
+ 为什么要把原子操作和锁一起用?
+ 我没能理解为什么要使用原子操作,试想如果o.Do已经成功执行过一次的情况,此时o.done的值为1,如果同时有100个线程调用o.Do,那么由于 atomic.LoadUint32(&o.done)是原子的,所以这一百个线程其实是排队依次读取这个值的,影响了并发性能;如果不用原子操作是不是会更好,这一百个线程就可以同时读取o.done的值;由于有锁,所以对o.done的赋值操作是单线程的,也没必要使用原子操作;修改后代码如下:
```
func (o *Once) Do(f func()) {
if o.done == 1 {
return
}
// Slow-path.
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
f()
o.done = 1
}
}
```
defer atomic.StoreUint32(&o.done, 1)是在Do返回之后执行,这个应该很有必要吧。我的1.14.1源码注释有说这个问题吧
```go
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