最近,我在很多Github库里看到这种类型的错误,单例模式的实现没有考虑线程安全,下面是常识性错误的代码
- package singleton
- type singleton struct {}
- var instance * singleton
- func GetInstance() * singleton {
- if instance == nil {
- instance = &singleton {} // <---非线程安全的
- }
- return instance
- }
我也看到一些使用糟糕的方法来解决线程安全的问题。事实上他解决了多线程的问题,但是创造了其他潜在的更严重的问题,他通过对整个方法执行锁定来引入线程竞争
- var mu Sync.Mutex
- func GetInstance() * singleton {
- mu.Lock() // <--- 如果实例已经被创建就没有必要锁写
- defer mu.Unlock()
- if instance == nil {
- instance = &singleton {}
- }
- return instance
- }
在c++和其他语言,用于保证最小锁定并且保证线程安全的最好、最安全的方式是当需要锁定时使用众所周知的Check-Lock-Check模式。下面的伪代码说明了这个模式的大概样子
- if check() {
- lock() {
- if check() {
- // perform your lock-safe code here
- }
- }
- }
- func GetInstance() *singleton {
- if instance == nil { // <-- 不够完善. 他并不是完全的原子性
- mu.Lock()
- defer mu.Unlock()
- if instance == nil {
- instance = &singleton{}
- }
- }
- return instance
- }
- import "sync"
- import "sync/atomic"
- var initialized uint32
- ...
- func GetInstance() *singleton {
- if atomic.LoadUInt32(&initialized) == 1 {
- return instance
- }
- mu.Lock()
- defer mu.Unlock()
- if initialized == 0 {
- instance = &singleton{}
- atomic.StoreUint32(&initialized, 1)
- }
- return instance
- }
但是.....我相信我们可以通过查看Go语言和标准库的源码看一下go routines 同步的实现方式来做的更好
我们想要使用Go的惯用手法来实现这个单例模式。所以我们需要看一下打包好的sync标准库。我们找到了 Once 类型。这个对象可以精确的只执行一次操作,下面就是Go标准库的代码
- // Once is an object that will perform exactly one action.
- type Once struct {
- m Mutex done uint32
- }
- // Do calls the function f if and only if Do is being called for the
- // first time for this instance of Once. In other words, given
- // var once Once
- // if once.Do(f) is called multiple times, only the first call will invoke f,
- // even if f has a different value in each invocation. A new instance of
- // Once is required for each function to execute.
- //
- // Do is intended for initialization that must be run exactly once. Since f
- // is niladic, it may be necessary to use a function literal to capture the
- // arguments to a function to be invoked by Do:
- // config.once.Do(func() { config.init(filename) })
- //
- // Because no call to Do returns until the one call to f returns, if f causes
- // Do to be called, it will deadlock.
- //
- // If f panics, Do considers it to have returned; future calls of Do return
- // without calling f.
- //
- func(o * Once) Do(f func()) {
- if atomic.LoadUint32( & o.done) == 1 { // <-- Check
- return
- }
- // Slow-path.
- o.m.Lock() // <-- Lock
- defer o.m.Unlock() if o.done == 0 { // <-- Check
- defer atomic.StoreUint32( & o.done, 1) f()
- }
- }
这意味着我们可以运用非常棒的 Go sync包来调用一个只执行一次的方法。因此,我们可以向下面这样调用 once.Do() 方法
- once.Do(func() {
- // 执行安全的初始化操作
- })
下面你可以看到使用sync.Once类型实现的单例实现的完整代码,用于同步访问GetInstance() 并保证我们的类型初始化只执行一次。
- package singleton
- import (
- "sync"
- )
- type singleton struct {
- }
- var instance *singleton
- var once sync.Once
- func GetInstance() *singleton {
- once.Do(func() {
- instance = &singleton{}
- })
- return instance
- }
来源: http://www.cnblogs.com/li-peng/p/7700184.html