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()
    }
}

让我们把你的问题分成两部分:

  • 单身汉
  • 原子学与Go记忆模型
  • 单身汉 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
    处理了安全所需的所有锁定

    原子学与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
    好吧,你不需要两者都使用。原子更像是一个“硬件”锁,是锁的替代品。它们在无锁算法中使用,老实说,这很难正确。一旦两者都使用,你可以检查它。所以,我不这么认为。