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

我很困惑,下面的片段是否完美?


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

    }

}


慕少森
浏览 98回答 1
1回答

九州编程

让我们将您的问题分解为两部分:单例原子和 Go 内存模型单例Go 有包级变量。这些是在任何东西有机会移动之前实例化的,因此,如果您在使用包后立即创建这些东西,您将免费获得一个单身人士。package somepackvar(  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 上的内存状态达到您所期望的状态。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go