面试题答案
一键面试sync.Once的数据结构
sync.Once
的数据结构在Go语言源码中定义如下:
type Once struct {
done uint32
m Mutex
}
done
字段:是一个uint32
类型,用于标记初始化是否完成。值为0表示未初始化,非0表示已初始化。在多线程环境下,它可以通过原子操作进行读写,以确保不同协程对其状态的准确感知。m
字段:是一个互斥锁Mutex
。用于在初始化操作时进行同步,保证同一时间只有一个协程能够执行初始化代码。
关键方法Do
的实现
sync.Once
的核心方法是Do
,其定义如下:
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()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
- 快速检查:在
Do
方法中,首先通过atomic.LoadUint32(&o.done)
原子地读取done
字段的值。如果done
为0,说明初始化尚未完成,进入慢速路径doSlow
;如果done
非0,说明已经初始化过,直接返回,避免再次进入慢速路径带来的锁开销。这种设计利用了原子操作的高效性,对于已经初始化完成的情况,大大减少了锁竞争。 - 慢速路径:在
doSlow
方法中,首先获取互斥锁o.m
,确保同一时间只有一个协程能够执行后续代码。然后再次检查o.done
是否为0,这是因为在获取锁之前,可能有其他协程已经完成了初始化。如果o.done
仍然为0,则执行初始化函数f()
,并在函数结束后,通过atomic.StoreUint32(&o.done, 1)
原子地将done
设置为1,表示初始化完成。这样,通过双重检查锁定机制和原子操作,确保了在高并发场景下初始化操作的原子性,避免了重复初始化问题。