关于go:Golang-单例模式与syncOnce

9次阅读

共计 1920 个字符,预计需要花费 5 分钟才能阅读完成。

Golang 单例模式与 sync.Once

背景

单例模式能够说是最简略的设计模式之一了,性能很简略:一个类型的货色只实例化一次,全局只有一个实例,并提供办法来获取该实例。

在 Golang 中变量或阐明实例只初始化一次的成果通过 init 函数是能够实现的,包在被引入时就会执行一次 init 函数且无论同一包被引入多少次也都只执行一次。

不过本文次要想探讨的单例模式是第一次须要用到时才去初始化,也就是提早初始化。

不太好的单例实现

// bad_singleton.go

package main

import ("sync")

var svcMu sync.Mutex
var svc *Svc

type Svc struct {Num int}

func GetSvc() *Svc {
    if svc == nil { // 这一步判断不是并发平安的
        svcMu.Lock()
        defer svcMu.Unlock()
        if svc == nil {svc = &Svc{Num: 1}
            svc = &Svc{}
            svc.Num = 1

        }
    }

    return svc
}

留神执行互斥锁 svcMu.Lock() 前的语句 if svc == nil 并不是并发平安的,即在多个 goroutine 并发调用的场景下,其中的一个 goroutine 正在初始化这个变量 svc 的过程中,这里别的 goroutine 判断失去 svc 不等于 nil 的后果时也并不意味着 svc 就肯定实现初始化了。

因为在不足显式同步的状况下,编译器和 CPU 在能保障每个 goroutine 内满足串行一致性的根底上能够自在地重排拜访内存的指令程序。

比方 svc = &Svc{Num: 1} 这行看上去只是一条执行语句,可能重排后的一种实现是像上面这样的:

svc = &Svc{}
svc.Num = 1

可见,不等于 nil 并不意味着就肯定实现了初始化,因而下面示例是一种不太好的单例实现。

比拟好的单例实现

// good_singleton.go

package main

import ("sync")

var svcOnce sync.Once
var svc *Svc

type Svc struct {Num int}

func GetSvc() *Svc {svcOnce.Do(func() {svc = &Svc{Num: 1}
    })

    return svc
}

sync.Once提供的 Do 办法无论被调用多少次都只执行传入的函数一次,那为什么说间接应用 Do 办法执行初始化而不是套一层 if svc == nil 才是比拟好的做法呢,上面联合 sync.Once 源码来阐明。

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

官网对于 sync.Once 的实现是十分短小精悍的。其中 atomic.LoadUint32(&o.done) == 0 是要害的一步,这里采纳的是原子操作语句,保障了即便在并发场景下也是平安的,对数据的读写都是残缺的。

o.done 的值为 0 时示意未进行初始化或正在初始化中,只有等于 1 时才示意初始化曾经实现,即 f() 执行实现后由 defer atomic.StoreUint32(&o.done, 1) 语句给 o.done 赋值 1;也就是o.done 作为是否实现初始化的标识,可能的值只有后面说的两个,为 0 时则加锁并尝试初始化流程,反之则视为已实现初始化间接跳过,这样就完满兼顾了效率与并发平安。

由此可见 sync.Once 内置的初始化实现标识判断远比 if svc == nil 靠谱,因而像下面这样应用 sync.Once 实现单例模式是最举荐的形式。

额定举荐

实则开发中用到的设计模式常常不止一种,越是简单大型的我的项目就越须要应用更多适合的模式来优化代码。

上面要举荐的是 RefactoringGuru。这是我所见过最好的设计模式教程,是国外创立的一个教程网站,有中文站点,图文并茂地介绍每一种模式的构造、关系和逻辑,
最重要的是示例代码方面囊括了常见的几种支流编程语言,是个适宜少数程序员学习设计模式的好中央!

下图是设计模式的目录页面(是不是很图文并茂呢):

结语

以上为自己学习和实际的一些总结,如有错漏还请不吝赐教。

参考

《Go 程序设计语言》9.5 提早初始化:sync.Once 网络版
Go 单例模式解说和代码示例

正文完
 0