Go &引用;“完美单身”;通过使用同步。一次?
我很困惑,下面的片段是否完美Go &引用;“完美单身”;通过使用同步。一次?,go,synchronization,Go,Synchronization,我很困惑,下面的片段是否完美 import "sync" import "sync/atomic" var initialized uint32 var instance *singleton var instance *singleton var once sync.Once func GetInstance() *singleton { once.Do(func() { instance = &singleton{} }) return
import "sync"
import "sync/atomic"
var initialized uint32
var instance *singleton
var instance *singleton
var once sync.Once
func GetInstance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}
atomic.StoreUint32(&initialized,1)
是否将实例刷新到所有CPU?
我想我需要添加一个原子存储和加载,比如下面的代码片段
var instance *singleton
var once sync.Once
func GetInstance() *singleton {
once.Do(func() {
atomic.StorePointer(&instance, &singleton{})
})
return atomic.LoadPointer(&instance)
}
我想一次。做的只是保证执行一次函数。 而
atomic.StoreUint32(&o.done,1)
是o.done的唯一内存屏障。
它不能确保实例
全局可见
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 1 {
return
}
// Slow-path.
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
让我们把你的问题分成两部分:
package somepack
var(
connection = createConn()
)
func Connection() SomeConnection {
return connection
}
connection
将创建一次,因此connection()
将安全地返回相同的实例
有时,当开发人员需要“惰性”实例化时,他们会使用单例。这是一个好主意,如果资源是昂贵的创建,并不总是需要的。这是sync.Once
有用的地方
var (
connection SomeConnection // Not instantiated
connectionOnce sync.Once
)
func Connection() SomeConnection {
connectionOnce.Do(func(){
connection = createConn()
})
return connection
}
注意,我没有对赋值做任何特殊的处理(例如,atomic.Store()
)。这是因为sync.Once
处理了安全所需的所有锁定
原子学与Go记忆模型
一个很好的资源是为此发布的文档:
您对“刷新”到不同CPU的关注是有效的(尽管有一些注释),因为每个CPU都有自己的缓存和自己的状态。C++(除了其他语言,如锈)开发人员往往关心这一点,因为他们得到。Go开发者没有那么在意,因为Go只有“以前发生过”。铁锈其实有一些好处
也就是说,你通常不需要担心它。互斥锁(和
sync.Once
)将强制每个CPU上的内存状态符合您的预期。什么是“完美单例”?大多数人认为独生子女是一种反模式,所以恰恰相反。你想解决一个问题吗?我不确定我是否理解这里的目标。这比简单地初始化实例
(如var instance=&singleton{}
)要好多少?包初始化(包括表达式求值和变量声明中的赋值)在单个goroutine中安全地进行,然后才能访问该变量。正如JimB所指出的,除非您真正的初始化更复杂,并且您只想按需执行,否则不需要代码功夫。即使您需要更复杂的初始化,使用原子将实例刷新到所有CPU也没有意义。你需要同步,sync.Once
提供了同步功能。我认为这并不像有些人想象的那么疯狂。每个CPU都有一个缓存,无锁算法通常必须担心CPU上的缓存可能有什么。话虽如此,这里没有必要。但我不认为说它毫无意义是件好事。我认为只有一次。我只保证执行函数f一次。而atomic.StoreUint32(&o.done,1)是o.done的唯一内存屏障。它不能确保实例是全局可访问的,但一旦使用锁,就会强制每个CPU刷新其缓存。如果锁会强制每个CPU刷新其缓存,为什么需要atomic.StoreUint32(&o.done,1)
?只需要o.done=1
好吧,你不需要两者都使用。原子更像是一个“硬件”锁,是锁的替代品。它们在无锁算法中使用,老实说,这很难正确。一旦两者都使用,你可以检查它。所以,我不这么认为。