我很困惑,代码段下面是完美的吗?
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)
}
我认为Once.Do仅保证一次执行函数f。
atomic.StoreUint32(&o.done, 1)
只是o.done的存储障碍。
它不能确保instance
是全局可见的
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()
}
}
答案 0 :(得分:1)
让我们将您的问题分解为两部分:
Go具有程序包级别的变量。在没有任何机会移动它们之前将它们实例化,因此,如果您在使用包后立即创建了这些东西,那么就可以免费获得一个单例。
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
负责确保安全性所需的所有锁定。
一个很好的开始资源是为此发布的文档:The Go Memory Model
您对“刷新”到不同CPU的担心是有效的(尽管有一些评论),因为每个CPU都有自己的具有各自状态的缓存。 C ++(在Rust等其他语言中)开发人员倾向于这样做,因为他们会这么做。 Go开发人员不必太在意AS,因为Go仅在“之前发生过”。实际上,Rust上面有一些不错的docs。
话虽这么说,通常您不必担心。互斥锁(和sync.Once
)将强制每个CPU上的内存状态符合您的期望。