Go 单例模式 sync.Once

Go 的单例可以用 sync.Once 简单实现:

var gObject *ClassName
var once sync.Once
func GetInstance() *ClassName {
    once.Do(func(){
        gObject = &ClassName{}
    })
    return gObject
}

sync.Once 的代码如下:

package sync
import (
    "sync/atomic"
)
type Once struct {
    done uint32
    m    Mutex
}

func (o *Once) Do(f func()) {
    if atomic.LoadUint32(&o.done) == 0 {
        o.doSlow(f)
    }
}

func (o *Once) doSlow(f func()) {
    o.m.Lock()
    defer o.m.Unlock()
    if o.done == 0 {
        defer atomic.StoreUint32(&o.done, 1)
        f()
    }
}
  • 正常的使用了锁,并且是”双重锁检查”模式

  • 有两个奇怪的实现点:

    1. 为什么用atomic.LoadUint32(&o.done)而不用atomic.CompareAndSwapUint32(&o.done, 0, 1)? Once 的语义是:”保证目标函数只执行一次”,如果这里在没有锁的情况下执行了,其他线程也可能执行,所以先不能设置”已执行”标识位。
    2. 为什么用defer更新o.done而不直接更新? 如果在f()后面放更新代码,如果发生了 panic,会导致更新代码没有走到;如果放在前面,实际上函数还没有执行,其他线程可能取得错误结果, 不符合”o.done 一旦设置就表示函数执行过一次”的语义