Go并发编程-Once
只执行一次。常用于单例对象的延迟初始化和并发访问下的只需一次初始化的共享资源或者在测试时候初始化测试资源。
简单用
sync.Once 只暴露了一个Do方法,多次调用时只会执行一次
func main() {
o := sync.Once{}
o.Do(GetConn)
o.Do(GetConn)
}
func GetConn() {
fmt.Println("获取连接")
}
分析以上代码:main方法中调用了两次do方法,但是GetConn只执行了一次。
看实现
type Once struct {
done uint32 //执行的标记
m Mutex //互斥锁,有并发goroutine执行时,进入doSlow方法
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
// 双检查机制,done==0则为第一次执行
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
别踩坑
-
死锁
once的嵌套使用,once中会调用f函数,但如果f函数中又调用了once.do,就会进入到无限递归当中。导致死锁
func main() { var o = sync.Once{} o.Do(func() { GetConn(&o) }) } func GetConn(o *sync.Once) { o.Do(func() { fmt.Println("do something") }) fmt.Println("获取连接") }
-
未初始化成功导致panic
第一次未初始化成功,以后也都不会初始化了。遇到此类问题可以扩展使用once,在调用do方法时再次尝试初始化
type Conn struct { flag int } func (c *Conn) DoSomething() { fmt.Println("conn's flag", c.flag) } func main() { var o = sync.Once{} var conn *Conn o.Do(func() { conn, _ = GetConn() }) conn.DoSomething() } func GetConn() (*Conn, error) { fmt.Println("获取连接") return nil, errors.New("获取失败") }
-
有时需要reset掉once,go推荐使用一个新的once实例来实现,但是使用新的once的时候可能会犯错
type MuOnce struct { sync.Once sync.RWMutex mtime time.Time vals []string } func (m *MuOnce) refresh() { m.Lock() defer m.Unlock() m.Once = sync.Once{} m.mtime = time.Now() m.vals = []string{m.mtime.String()} } //超过时间就重新初始化 func (m *MuOnce) strings() []string { now := time.Now() m.Lock() if now.After(m.mtime) { defer m.Do(m.refresh) } vals := m.vals m.Unlock() return vals }
以上代码分析:以上代码会panic,原因是在refersh函数中,更新了sync.Once的指针,势必会导致unlock一个未加锁的mutex
扩展用
-
Once.Do失败后,调用Do时再次尝试初始化
type once struct { done uint32 sync.Mutex } func (o *once) Do(f func() error) error { if atomic.LoadUint32(&o.done) == 1 { return nil } return o.slowDo(f) } func (o *once) slowDo(f func() error) error { o.Lock() defer o.Unlock() var err error if o.done == 0 { // 如果有错误就没有赋值,下次再调用Do时还会初始化 err = f() if err == nil { atomic.StoreUint32(&o.done, 1) } } return err }
-
once并没有返回当前的状态,不知道是否初始化过,可以自己扩展一个用来标记是否初始化的,这样方便查看是否有初始化过
type Once struct { sync.Once } func (o *Once) Done() bool { return atomic.LoadUint32((*uint32)(unsafe.Pointer(&o.Once))) == 1 }