Go Singleton pattern [Personal Translator]

  Original Address: http://marcio.io/2015/07/singleton-pattern-in-go/  

  Go the last few years the growth rate is very alarming language, attracts all walks of life to switch to the Go language. Recently, many companies on the use of the Ruby language to switch to Go, Go language experience, and Go parallelism and concurrency problem-solving article.

  Over the past 10 years, Ruby on Rails has so many developers and start-up companies to quickly develop a powerful system, most of the time do not need to worry about how his internal works, or worry about thread safety and concurrency. RoR programs rarely run parallel threads and create something. Infrastructure and hosting the entire stack frame uses a different method for parallel by multiple processes. In recent years, such as Puma multi-threaded rack-mounted servers became popular, but even so, it also brings the beginning of a lot of questions about the use of third-party and other gems are not designed to be thread-safe code.

     There are a lot of developers to start using the Go language. We need to examine our code, and observe the behavior of the code, need to be thread-safe manner the code reset code.

Common Mistake

   Recently, I saw a lot of this type of error in the Github repository, single-case model does not consider thread safety, the following code is a common mistake

  

package singleton

type singleton struct {
}

var instance *singleton

func GetInstance() *singleton {
    if instance == nil {
        instance = &singleton{}   // <---非线程安全的
    }
    return instance
}

  

  In the above example, a plurality of first go routines will be checked and the type of singleton instance is created and covering each other. Which is no guarantee that an instance will be returned, in this example further operation and developers expect possible to vary.

  This is problematic because if a single instance of this embodiment has been applied in the code, there may be multiple instances of the potential of this type, and use a respective states, potentially different behavior of the code. He may also become a nightmare when the height of error and difficult to locate, because due to the suspension of the implementation of the potential reduction in non-thread-safe runtime error does not occur in real time debug, it is easy to hide developers.

Radical lock

  I also saw some of the worst method to solve thread safety issues. In fact, he solves the problem of multi-threading, but create other potentially more serious problem, he is introduced to the competition by executing thread lock on the entire method

var mu Sync.Mutex

func GetInstance() *singleton {
    mu.Lock()                    // <--- 如果实例已经被创建就没有必要锁写
    defer mu.Unlock()

    if instance == nil {
        instance = &singleton{}
    }
    return instance
}

  The above code, we can see that, to solve the problem by introducing a thread-safe Sync.Mutex, and acquires the lock before creating a singleton instance. The problem is that when we do not need time for example, instances have been created, only to return singleton instance cache, but it also performs a lock operation. In highly concurrent code base, which create a bottleneck, because at the same time only a single instance go routine embodiment can be obtained.

     So this is not the best way, we look for other solutions.

 Check-Lock-Check mode

   In c ++ and other languages, to ensure minimal locking and ensure the best and safest way to thread-safe when using known when the need to lock Check-Lock-Check mode. The following pseudo code illustrates this pattern probably look

if check() {
    lock() {
        if check() {
            // perform your lock-safe code here
        }
    }
}

 

  The idea behind this model is to start checking. To reduce any aggressive locked. Because an IF statement is much cheaper than the lock. Second, we want to wait and get an exclusive lock so within the same time block only one execution, but between the first and prosecutorial and get exclusive locks may be other threads that want to acquire a lock, so we need within a block check again to avoid a single instance be replaced by other examples of embodiment.

     Over the years, and I work with people familiar with this, in the code review process, this thought pattern and thread safety, I am very hard on the team.

     If we apply this model to my GetInstance () method, we need to do the following:

func GetInstance() *singleton {
    if instance == nil {     // <-- 不够完善. 他并不是完全的原子性
        mu.Lock()
        defer mu.Unlock()

        if instance == nil {
            instance = &singleton{}
        }
    }
    return instance
}

  

  This is a very good method, but not perfect. Because the compiler optimization, but no checking atoms saved instance state. Comprehensive technical considerations, this is not the security of the United States. However, previous methods have been a lot better.   

  However, the use of sync / atomic packet, we can set the loading and atomic identifier indicating whether initialized our example.

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
}

 

   But ..... I believe we can look at by looking at the Go language and standard library source code go routines synchronized to achieve better way to do

 Example Go conventional single

   We want to use Go's modus operandi to achieve this singleton pattern. So we need to look at packaged sync standard library. Once we found the type. This object can accurately perform only one operation, the following is the code Go standard library

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

 

  This means we can use great Go sync package to invoke a method that is performed only once. Therefore, we can call once.Do () method to follows

once.Do(func() {
    // 执行安全的初始化操作
})

  Below you can see the complete code for a single use case realization sync.Once type of implementation, for synchronous access GetInstance () and to ensure that our type initialization is performed only once.

package singleton

import (
    "sync"
)

type singleton struct {
}

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
    once.Do(func() {
        instance = &singleton{}
    })
    return instance
}

 

  Thus, a package is used sync.Once perfect security implementation, such a bit like Object-C and Swift (Cocoa) dispatch_once implemented method for performing a similar initializing operation.

     to sum up

     When it comes to parallel and concurrent code requires detailed examination of your code. Always let your team code review, so for such things can be more easily monitored.

     All new developers to switch to the Go language, we need a clear understanding of the principles of thread safety in order to better improve your code. Go even language itself by making a lot of efforts allows you to use very little knowledge of concurrent design concurrent code. There are still some language can not help you deal with some of the situations, you still need to apply the best practices in the development of the code

 

Guess you like

Origin blog.csdn.net/mi_duo/article/details/83822500