i am confusing that, is below snippet perfect?
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)
will flush instance to all CPUs? i think i need add an atomic store and load for instance, like below snippet
var instance *singleton
var once sync.Once
func GetInstance() *singleton {
once.Do(func() {
atomic.StorePointer(&instance, &singleton{})
})
return atomic.LoadPointer(&instance)
}
i think Once.Do is only guarantee execute function f one time. and atomic.StoreUint32(&o.done, 1)
is only memory barrier for o.done. it doesn't ensure instance
is global visible
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()
}
}
Lets break down your question to two pieces:
Go has package level variables. These are instantiated before anything has the chance to get moving, therefore if you good with these things being created as soon as the package is used, you get a singleton for free.
package somepack
var(
connection = createConn()
)
func Connection() SomeConnection {
return connection
}
connection
will be created once and therefore Connection()
will return the same instance of it safely.
Sometimes developers reach for a singleton when they want "lazy" instantiation. This is a good idea if the resource is expensive to create and not always needed. This is where sync.Once
is useful.
var (
connection SomeConnection // Not instantiated
connectionOnce sync.Once
)
func Connection() SomeConnection {
connectionOnce.Do(func(){
connection = createConn()
})
return connection
}
Notice I'm not doing anything special with the assignment (e.g., atomic.Store()
). This is because sync.Once
takes care of all the locking required for this to be safe.
A good resource to start with is the published docs for this: The Go Memory Model
Your concern of "flushing" to the different CPUs is valid (despite some of the comments) because each CPU has its own cache with its own state. C++ (among other languages like Rust) developers tend to care about this because they get to. Go developers don't get to care AS much because Go only has "happens before". Rust in fact has some nice docs on it.
That being said, you normally don't need to worry about it. A mutex (and sync.Once
) will force the state of the memory on each CPU to be what you would expect.