通过使用sync来“完美的单例”。 [关闭]

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:

  1. Singletons
  2. Atomics and the Go memory model

Singletons

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.

Atomics and the Go memory model

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.