为了账号安全,请及时绑定邮箱和手机立即绑定

通过使用 sync.Once 实现“完美单例”?

通过使用 sync.Once 实现“完美单例”?

Go
慕少森 2023-05-15 09:52:51
我很困惑,下面的片段是否完美?import "sync"import "sync/atomic"var initialized uint32var instance *singletonvar instance *singletonvar once sync.Oncefunc GetInstance() *singleton {    once.Do(func() {        instance = &singleton{}    })    return instance}atomic.StoreUint32(&initialized, 1)会将实例刷新到所有 CPU 吗?我想我需要添加一个原子存储和加载,例如下面的代码片段var instance *singletonvar once sync.Oncefunc 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 回答

?
九州编程

TA贡献1785条经验 获得超4个赞

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

  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 开发人员不会太在意,因为 Go 只有“以前发生过”。Rust 实际上有一些不错的文档。


话虽这么说,你通常不需要担心它。互斥锁(和sync.Once)将强制每个 CPU 上的内存状态达到您所期望的状态。


查看完整回答
反对 回复 2023-05-15
  • 1 回答
  • 0 关注
  • 87 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信