面试题答案
一键面试sync.Once
内部结构:sync.Once
结构体定义如下:
type Once struct { done uint32 m Mutex }
done
字段是一个uint32
类型,用于标记初始化是否已经完成。当done
为0时,表示尚未初始化;非0时,表示已经初始化。m
字段是一个互斥锁,用于在多协程环境下保护对done
的读写操作以及初始化函数的执行。
Do
方法实现延迟初始化和保证只执行一次:sync.Once
的核心方法是Do
,其定义为:
func (o *Once) Do(f func()) { if atomic.LoadUint32(&o.done) == 0 { o.doSlow(f) } }
- 首先,通过
atomic.LoadUint32(&o.done)
原子操作读取done
的值。如果done
已经非0,说明初始化已经完成,直接返回,不再执行初始化函数f
。 - 如果
done
为0,调用doSlow
方法:
func (o *Once) doSlow(f func()) { o.m.Lock() defer o.m.Unlock() if o.done == 0 { defer atomic.StoreUint32(&o.done, 1) f() } }
- 在
doSlow
方法中,先获取互斥锁o.m
,这样可以确保同一时间只有一个协程能够进入初始化逻辑。 - 再次检查
o.done
是否为0,这是因为在获取锁之前,可能其他协程已经完成了初始化。 - 如果
o.done
仍为0,使用defer atomic.StoreUint32(&o.done, 1)
在初始化函数f
执行完毕后,将done
设置为1,表示初始化完成。然后执行初始化函数f
。
总结来说,sync.Once
通过done
字段标记初始化状态,利用原子操作和互斥锁,在多协程环境下保证初始化操作只执行一次,实现延迟初始化。