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

  1. Build a sample structure
   type example struct {
   	name string
   }
Copy the code
  1. Set a private variable to be returned each time as a singleton
  var instance *example
Copy the code
  1. 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
  1. 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 ~