go如何实现一个单例

理解单例:

在程序运行的周期内,只初始化一次,称为单例

在go中,我们可以使用两种方法来实现单例。

  1. init 方法
  2. sync.Once

init非常地简单,在程序运行的时候,提前把我们需要的资源初始化好,就可以实现一个简单单例了。

但是在程序的其他地方,如何控制只实现一次?

这里就需要使用到sync.Once.

先看单例的实现

type Singleton struct {
        t int64
}
var once sync.Once
var singleton *Singleton // 单例
func GetInstance() *Singleton {
        once.Do(func() {
                t := time.Now().UnixNano()
                singleton = &Singleton{
                            t: t,
                }
        })
        return singleton
}

上面就实现了一个单例

为何sync.Once可以实现

先来看看go 1.13.5下Once的实现

type Once struct {
        // done indicates whether the action has been performed.
        // It is first in the struct because it is used in the hot path.
        // The hot path is inlined at every call site.
        // Placing done first allows more compact instructions on some architectures (amd64/x86),
        // and fewer instructions (to calculate offset) on other architectures.
        done uint32
        m    Mutex
}

func (o *Once) Do(f func()) {
    // Note: Here is an incorrect implementation of Do:
    //
    //  if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
    //      f()
    //  }
    //
    // Do guarantees that when it returns, f has finished.
    // This implementation would not implement that guarantee:
    // given two simultaneous calls, the winner of the cas would
    // call f, and the second would return immediately, without
    // waiting for the first's call to f to complete.
    // This is why the slow path falls back to a mutex, and why
    // the atomic.StoreUint32 must be delayed until after f returns.

    if atomic.LoadUint32(&o.done) == 0 {
        // Outlined slow-path to allow inlining of the fast-path.
        o.doSlow(f)
    }
}

Do里面的注释非常有意思,不仅说明了如何实现只执行一次,还给出了一个错误的示范。

Do的实现中,会去判断done的值是否是0,如果是0的话,才可以进行初始化。

查看doSlow(f func())的实现

func (o *Once) doSlow(f func()) {
        o.m.Lock()
        defer o.m.Unlock()
        if o.done == 0 {
                defer atomic.StoreUint32(&o.done, 1)
                f()
        }
}

这里面进来就会加一个互斥锁,然后又一次判断done的值是0,才开始将传入的方法f()进行执行。再后面一步是把done的值设置为1,再释放掉互斥锁。

在生命周期内,done == 1,一直存在,所以f()就不会被再次执行。

验证sync.Once的效果

package singleton
import (
        "sync"
        "time"
)
type Singleton struct {
        t int64
}
type NSingleton struct {
        t int64
}
var once sync.Once
var singleton *Singleton // 单例
var nSingleton *NSingleton // 非单例
func GetInstance() *Singleton {
        once.Do(func() {
                t := time.Now().UnixNano()
                singleton = &Singleton{
                        t: t,
                }
        })
        return singleton
}
func GetNSingletonInstance() *NSingleton {
        t := time.Now().UnixNano()
        nSingleton = &NSingleton{
                t: t,
        }
    return nSingleton
}

测试文件

package singleton
// go test singleton_test.go singleton.go -json
import (
        "testing"
        "time"
)

func TestGetInstance(t *testing.T) {
        t1 := GetInstance().t
        time.Sleep(time.Second * 2)
        t2 := GetInstance().t

        t.Log(t1, t2)
                if t1 == t2 {
                t.Log("singleton ok")
        } else {
                t.Error("singleton err")
        }
}

func TestGetNSingletonInstance(t *testing.T) {
        t1 := GetNSingletonInstance().t
        time.Sleep(time.Second * 2)
        t2 := GetNSingletonInstance().t

        t.Log(t1, t2)
        if t1 == t2 {
                t.Error("n singleton ok")
        } else {
                t.Log("n singleton err")
        }
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注

评论审核已启用。您的评论可能需要一段时间后才能被显示。