go

sync.Once

sync.Once源码分析

Posted by Liangjf on July 20, 2020

sync.Once源码分析

sync.Once是借助原子变量和锁来实现函数只执行一次的功能.

通过上锁, 避免首次并发竞争, 以后是通过atomic来cas获取变量状态来判断是否已执行, 提高效率

由于首次doSlow会上锁, 因此首次并发情况下, 会出现其他goroutine等待函数执行完并设置了done标志, 这里会有一个延时等待,

但是结合Once的使用, 一般是作为初始化时使用, 并发基本没有或者一般很低, 所以性能是没问题的.

```go
package sync

import (
	"sync/atomic"
)


type Once struct {
	// done 作为原子变量, cas方式标记已调用
	done uint32
	m    Mutex
}


func (o *Once) Do(f func()) {
	//两个goroutine同时调用, 其中一个goroutine会原子等待另外一个goroutine的结束
	//因此并发调用Once时, 注意因并发而发生延迟问题
	if atomic.LoadUint32(&o.done) == 0 {
		o.doSlow(f)
	}
}

//doSlow 可以看到doSlow 会先上锁, 然后执行完f()才会cas设置原子变量
//所以这里如果是并发调用once, 会出现因上锁又等待done为非0的情况
func (o *Once) doSlow(f func()) {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		//cas设置done变量
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}
```