通过使用sync来“完美单身”。

时间:2018-11-27 14:38:26

标签: 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 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()
    }
}

1 个答案:

答案 0 :(得分:1)

让我们将您的问题分解为两部分:

  1. 单子
  2. 原子与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记忆模型

一个很好的开始资源是为此发布的文档:The Go Memory Model

您对“刷新”到不同CPU的担心是有效的(尽管有一些评论),因为每个CPU都有自己的具有各自状态的缓存。 C ++(在Rust等其他语言中)开发人员倾向于这样做,因为他们会这么做。 Go开发人员不必太在意AS,因为Go仅在“之前发生过”。实际上,Rust上面有一些不错的docs

话虽这么说,通常您不必担心。互斥锁(和sync.Once)将强制每个CPU上的内存状态符合您的期望。