乐趣区

关于go:Golang-常见设计模式之单例模式

之前咱们曾经看过了 Golang 常见设计模式中的装璜和选项模式,明天要看的是 Golang 设计模式里最简略的单例模式。单例模式的作用是确保无论对象被实例化多少次,全局都只有一个实例存在。依据这一个性,咱们能够将其利用到全局唯一性配置、数据库连贯对象、文件拜访对象等。Go 语言实现单例模式的办法有很多种,上面咱们就一起来看一下。

饿汉式

饿汉式实现单例模式非常简单,间接看代码:

package singleton
type singleton struct{}
var instance = &singleton{}
func GetSingleton() *singleton {return instance}

singleton 包在被导入时会主动初始化 instance 实例,应用时通过调用 singleton.GetSingleton () 函数即可取得 singleton 这个构造体的单例对象。

这种形式的单例对象是在包加载时立刻被创立,所以这个形式叫作饿汉式。与之对应的另一种实现形式叫作懒汉式,懒汉式模式下实例会在第一次被应用时被创立。

须要留神的是,只管饿汉式实现单例模式的形式简略,但大多数状况下并不举荐。因为如果单例实例化时初始化内容过多,会造成程序加载用时较长。

懒汉式

接下来咱们再来看下如何通过懒汉式实现单例模式:

package singleton
type singleton struct{}
var instance *singleton
func GetSingleton() *singleton {
    if instance == nil {instance = &singleton{}
    }
    return instance
}

相较于饿汉式的实现,懒汉式将实例化 singleton 构造体局部的代码移到了 GetSingleton () 函数外部。这样可能将对象实例化的步骤提早到 GetSingleton () 第一次被调用时。

不过通过 instance == nil 的判断来实现单例并不非常牢靠,如果有多个 goroutine 同时调用 GetSingleton () 就无奈保障并发平安。

反对并发的单例

如果你应用 Go 语言写过并发编程,应该很快能想到该如何解决懒汉式单例模式并发平安问题,比方像上面这样:

package singleton
import "sync"
type singleton struct{}
var instance *singleton
var mu sync.Mutex
func GetSingleton() *singleton {mu.Lock()
    defer mu.Unlock()
    if instance == nil {instance = &singleton{}
    }
    return instance
}

下面代码的批改是通过加锁机制,即在 GetSingleton () 函数最开始加了如下两行代码:

mu.Lock()
defer mu.Unlock()

加锁的机制能够无效保障这个实现单例模式的函数是并发平安的。

不过应用了锁机制也带来了一些问题,这让每次调用 GetSingleton () 时程序都会进行加锁、解锁的步骤,从而导致程序性能的降落。

双重锁定

加锁会导致程序性能降落,但又不必锁又无奈保障程序的并发平安。为了解决这个问题有人提出了双重锁定(Double-Check Locking)的计划:

package singleton
import "sync"
type singleton struct{}
var instance *singleton
var mu sync.Mutex
func GetSingleton() *singleton {
    if instance == nil {mu.Lock()
        defer mu.Unlock()
        if instance == nil {instance = &singleton{}
        }
    }
    return instance
}

通过下面的能够看到,所谓双重锁定实际上就是在程序加锁前又加了一层 instance == nil 判断,通过这种形式来兼顾性能和平安两个方面。不过这让代码看起来有些奇怪,外层曾经判断了 instance == nil,然而加锁后又进行了第二次 instance == nil 判断。

其实外层的 instance == nil 判断是为了进步程序的执行效率,免去原来每次调用 GetSingleton () 都上锁的操作,将加锁的粒度更加精细化。简略说就是如果 instance 曾经存在,则无需进入 if 逻辑,程序间接返回 instance 即可。而内层的 instance == nil 判断则思考了并发平安,思考到万一在极其状况下,多个 goroutine 同时走到了加锁这一步,内层判断会在这里起到作用。

Gopher 习用计划

尽管双重锁定机制兼顾和性能和并发平安,但显然代码有些俊俏,不合乎宽广 Gopher 的期待。好在 Go 语言在 sync 包中提供了 Once 机制可能帮忙咱们写出更加优雅的代码:

package singleton
import "sync"
type singleton struct{}
var instance *singleton
var once sync.Once
func GetSingleton() *singleton {once.Do(func() {instance = &singleton{}
    })
    return instance
}

Once 是一个构造体,在执行 Do 办法的外部通过 atomic 操作和加锁机制来保障并发平安,且 once.Do 可能保障多个 goroutine 同时执行时 &singleton {} 只被创立一次。

其实 Once 并不神秘,其外部实现跟下面应用的双重锁定机制十分相似,只不过把 instance == nil 换成了 atomic 操作,感兴趣的同学能够查看下其对应源码。

总结

以上就是 Go 语言中实现单例模式的几种罕用套路,通过比照能够得出结论,最举荐的形式是应用 once.Do 来实现,sync.Once 包帮咱们暗藏了局部细节,却能够让代码可读性失去很大晋升。想要试验的搭档能够在 3A 云服务器上部署一套环境,本人入手敲一遍,加深了解。

退出移动版