fly*_*101 -3 synchronization go
我很困惑,下面的代码片段完美吗?
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
}
Run Code Online (Sandbox Code Playgroud)
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)
}
Run Code Online (Sandbox Code Playgroud)
我认为 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()
}
}
Run Code Online (Sandbox Code Playgroud)
让我们把你的问题分成两部分:
Go 有包级变量。这些是在任何东西有机会移动之前实例化的,因此,如果您擅长在使用包后立即创建这些东西,您将免费获得一个单例。
package somepack
var(
connection = createConn()
)
func Connection() SomeConnection {
return connection
}
Run Code Online (Sandbox Code Playgroud)
connection将被创建一次,因此Connection()将安全地返回它的相同实例。
有时,当开发人员想要“惰性”实例化时,他们会选择单例。如果创建资源的成本昂贵且并不总是需要,那么这是一个好主意。这就是sync.Once有用的地方。
var (
connection SomeConnection // Not instantiated
connectionOnce sync.Once
)
func Connection() SomeConnection {
connectionOnce.Do(func(){
connection = createConn()
})
return connection
}
Run Code Online (Sandbox Code Playgroud)
请注意,我没有对作业做任何特殊的事情(例如,atomic.Store())。这是因为sync.Once它负责确保安全所需的所有锁定。
已发布的文档是一个很好的入门资源:Go 内存模型
您对“刷新”到不同 CPU 的担忧是有效的(尽管有一些注释),因为每个 CPU 都有自己的缓存和自己的状态。C++(以及 Rust 等其他语言)开发人员往往会关心这一点,因为他们会关心这一点。Go 开发人员不会太关心,因为 Go 只“发生过”。事实上 Rust 有一些很好的文档。
话虽如此,您通常不需要担心它。互斥锁(和sync.Once)将强制每个 CPU 上的内存状态达到您所期望的状态。