介绍
在日常工作中, 单例模式的应用场景很多, 比如加载应用配置, 只需加载一次即可, 即:同一个类只实例化一个对象, golang中的单例模式是通过sync.Once(f func())来实现的。
源码解析
type Once struct {
// done indicates whether the action has been performed.
// It is first in the struct because it is used in the hot path.
// The hot path is inlined at every call site.
// Placing done first allows more compact instructions on some architectures (amd64/386),
// and fewer instructions (to calculate offset) on other architectures.
done uint32
m Mutex
}
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()
}
}
Once结构体由互斥锁m和无符号整型变量done组成, done标识着f是否执行完成, 完成则置为1。
Do函数中由原子操作 atomic.LoadUint32(&o.done) == 0判断done标志位, 那么这里有一个问题,可否使用atomic.CompareAndSwapUint32(&o.done, 0, 1)来替换atomic.LoadUint32(&o.done) == 0这个操作呢? 答案是否定的。
atomic.CompareAndSwapUint32(&o.done, 0, 1)
CAS的作用是判断done的值是否为0, 若为0则将其置为1, 并且返回true;否则,直接返回false。这里的done状态的变更是前置的, 也就意味着, 在并发的场景中, 就会导致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()
}
}
在doSlow函数中
if o.done == 0
这个判断是很有必要的, 因为在并发场景中, 两个协程都执行了atomic.LoadUint32(&o.done) == 0这个判断, 并且结果为true, 如果在doSlow中不做二次校验就会new出两个对象。
本文详细介绍了Golang中单例模式的实现,通过sync.Once结构和doSlow函数确保在并发情况下仅实例化一个对象,避免并发问题。重点讨论了atomic.LoadUint32与atomic.CompareAndSwapUint32的区别及其在单例模式中的正确用法。
1062

被折叠的 条评论
为什么被折叠?



