The singleton pattern
Singleton, also known as the monad pattern, is a common software design pattern. When this pattern is applied, the singleton class must ensure that only one instance exists. Many times the whole system only needs to have one global object, which helps us coordinate the behavior of the whole system. For example, in a server program, the configuration information of the server is stored in a file. The configuration data is read by a singleton and then retrieved by other objects in the server process. This approach simplifies configuration management in complex environments.
The effect of the singleton pattern is that there is only one instantiation object in the entire program for classes that apply the singleton pattern
Go is not an object-oriented language, so we use constructs instead
There are several ways:
-
Lazy mode
-
The hungry mode
-
Double-checked locking mechanism
Here’s a breakdown:
Lazy mode
- Build a sample structure
type example struct {
name string
}
Copy the code
- Set a private variable to be returned each time as a singleton
var instance *example
Copy the code
- Write a method to get a singleton
func GetExample(a) *example {
// There is a thread-safety issue, with high concurrency it is possible to create multiple objects
if instance == nil {
instance = new(example)
}
return instance
}
Copy the code
-
Test the
func main(a) { s := GetExample() s.name = "First assignment singleton pattern" fmt.Println(s.name) s2 := GetExample() fmt.Println(s2.name) } Copy the code
Lazy mode has thread safety issues. In step 3, if multiple threads call this method at the same time, instance nil will be detected and multiple objects will be created, so hungry mode appears…
The hungry mode
Similar to lazy mode, no more talking, go straight to the code
// Build a structure to instantiate a singleton
type example2 struct {
name string
}
// Declare a private variable as a singleton
var instance2 *example2
The init function is executed when the package is initialized to instantiate the singleton
func init(a) {
instance2 = new(example2)
instance2.name = "Initialize the singleton pattern"
}
func GetInstance2(a) *example2 {
return instance2
}
func main(a) {
s := GetInstance2()
fmt.Println(s.name)
}
Copy the code
The hungry mode creates a singleton object as soon as the package is loaded, wasting space when the object is not used in the program
Compared to slacker mode, it is safer, but slows down application startup
Double check mechanism
Lazy mode has thread-safety issues, and generally we use mutex to solve the possible data inconsistencies
So modify the GetInstance() method above as follows:
var mux Sync.Mutex
func GetInstance(a) *example {
mux.Lock()
defer mux.Unlock()
if instance == nil {
instance = &example{}
}
return instance
}
Copy the code
If you do this, every time you request a singleton, you add and subtract locks, and the use of locks is just to solve the concurrency problems that might occur when the object is initialized. When the object is created, locking is meaningless, it slows things down, So we introduced the check-lock-check mechanism, also known as DCL(Double Check lock), code as follows:
func GetInstance(a) *example {
if instance == nil { // If the singleton is not instantiated, it will be locked
mux.Lock()
defer mux.Unlock()
if instance == nil { // A singleton is created only if it is not instantiated
instance = &example{}
}
}
return instance
}
Copy the code
Lock and unlock operations are performed only when the object is not initialized
However, there is another problem: every access is checked twice. To solve this problem, atomic operations can be performed using methods in the Golang standard package:
import "sync"
import "sync/atomic"
var initialized uint32
func GetInstance(a) *example {
// Return with a single judgment
if atomic.LoadUInt32(&initialized) == 1 {
return instance
}
mux.Lock()
defer mux.Unlock()
if initialized == 0 {
instance = &example{}
atomic.StoreUint32(&initialized, 1) // Atomic load
}
return instance
}
Copy the code
The above code only needs one judgment to return the singleton, but the Golang standard package actually gives us the related method:
The Do method of sync.once allows the callback to run only Once during program execution, so the resulting code simplification is as follows:
type example3 struct {
name string
}
var instance3 *example3
var once sync.Once
func GetInstance3(a) *example3 {
once.Do(func(a) {
instance3 = new(example3)
instance3.name = "First assignment singleton"
})
return instance3
}
func main(a) {
e1 := GetInstance3()
fmt.Println(e1.name)
e2 := GetInstance3()
fmt.Println(e2.name)
}
Copy the code
The singleton pattern is a common design pattern used in development. I used this pattern in environment variable control, configuration item control, and so on when I created my Web framework Silsuer/Bingo.
I wanted to implement all of my design patterns in Golang, so I opened a new pit called Silsuer/Golang-Design-Patterns. This is the first post and will be updated later
The source code for this article is in this repository:Golang design pattern
Go web framework bingo, for star, for PR ~