The singleton pattern is a commonly used strategy for software development. This involves restricting the number of instances of a certain type to one, using the same instance across the whole application. A very simple implementation of the concept could be the following code:
type obj struct {}
var instance *obj
func Get() *obj{
if instance == nil {
instance = &obj{}
}
return instance
}
This is perfectly fine in a consecutive scenario but in a concurrent one, like in many Go applications, this is not thread-safe and could generate race conditions.
The previous example could be made thread-safe by adding a lock that would avoid any race condition, as follows:
type obj struct {}
var (
instance *obj
lock sync.Mutex
)
func Get() *obj{
lock.Lock()
defer lock.Unlock()
if instance == nil {
instance = &obj{}
}
return instance
}
This is safe, but slower, because Mutex will be synchronizing each time the instance is requested.
The best solution to implement this pattern, as shown in the following example, is to use the sync.Once struct that takes care of executing a function once using a combination of Mutex and atomic readings (which we will see in the second part of the chapter):
type obj struct {}
var (
instance *obj
once sync.Once
)
func Get() *obj{
once.Do(func(){
instance = &obj{}
})
return instance
}
The resulting code is idiomatic and clear, and has better performance compared to the mutex solution. Since the operation will be executed just the once, we can also get rid of the nil check we were doing on the instance in the previous examples.