GoLang design pattern 04 - singleton pattern

Posted by kellog on Thu, 27 Jan 2022 04:26:42 +0100

Singleton pattern is probably the most well-known design pattern. It is also a kind of creative mode. We will use this design pattern when only one instance of a struct is allowed. The only instance of this struct is called a singleton object. Here are some scenarios where you need to create a singleton object:

  • Database instance: generally, in development, we only need one database object instance for an application
  • Log instance: similarly, for an application, only one instance of log operation object is required

Singleton objects are usually created when struct is initialized. Usually, if a struct only needs to create one instance, it will define a getInstance() method for it, and the created singleton instance will be returned to the caller through this method.

Because there are goroutines in Go language, it will bring some trouble to the application of singleton mode. When building the singleton pattern, we must consider that multiple goroutines should return the same instance when accessing the getInstance() method of struct. The following code demonstrates how to correctly create a singleton object:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var lock = &sync.Mutex{}   type single struct { }   var singleInstance *single   func getInstance() *single {     if singleInstance == nil {         lock.Lock()         defer lock.Unlock()         if singleInstance == nil {             fmt.Println("Creting Single Instance Now")             singleInstance = &single{}         } else {             fmt.Println("Single Instance already created-1")         }     } else {         fmt.Println("Single Instance already created-2")     }     return singleInstance }

The above code ensures that there will only be one instance of single # struct. There are several points to note in the code:

  1. At the beginning of getInstance() method, first check whether the next singleInstance is nil. In this way, the "lock" operation can be avoided every time the getInstance() method is called. Because "lock" related operations consume resources and affect performance, the fewer calls the better.
  2. singleInstance objects are created within the "lock" action range to avoid the influence of goroutines.
  3. After obtaining the "lock" resource, the program verifies whether the singleInstance object is empty again. This is because multiple goroutines may pass the first verification. The second verification can ensure that only one goroutine creates a single instance. Otherwise, each goroutine may create a single # struct instance.

The complete code is here:

single.go

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import (     "fmt"     "sync" )   var lock = &sync.Mutex{}   type single struct { }   var singleInstance *single   func GetInstance() *single {     if singleInstance == nil {         lock.Lock()         defer lock.Unlock()         if singleInstance == nil {             fmt.Println("Creating Single Instance Now")             singleInstance = &single{}         } else {             fmt.Println("Single Instance already created-1")         }     } else {         fmt.Println("Single Instance already created-2")     }     return singleInstance }

  main.go

1 2 3 4 5 6 7 8 9 10 11 12 import (     "fmt" )   func main() {     for i := 0; i < 100; i++ {         go GetInstance()     }     // Scanln is similar to Scan, but stops scanning at a newline and     // after the final item there must be a newline or EOF.     fmt.Scanln() }

Output content:

1 2 3 4 5 6 7 8 9 10 11 12 Creating Single Instance Now Single Instance already created-1 Single Instance already created-2 Single Instance already created-1 Single Instance already created-1 Single Instance already created-1 Single Instance already created-1 Single Instance already created-1 Single Instance already created-1 Single Instance already created-2 Single Instance already created-2 ...

For a brief description:

  • There is only one line of "Creating Single Instance Now" in the output content, which indicates that only one goroutine can create an instance of singlestruct.
  • There are multiple lines of "Single Instance already created-1" in the output content, which indicates that multiple goroutines have passed the verification of checking whether the singleInstance object is empty for the first time. It would have had the opportunity to create a single instance.
  • The final output is "Single Instance already created-2", which means that the single instance has been created, and the subsequent goroutines cannot pass the first verification.

In addition to the lock + secondary verification method, there are other methods to create a single instance. Let's take a look:

Based on init() function

Create a singleton in the init() function. Because the init () function of each file in a package will be called only once, it can ensure that only one instance will be created. Look at the code below:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import (     "fmt"     "log" )   type single struct { }   var singleInstance *single   func init() {     fmt.Println("Creating Single Instance Now")     singleInstance = &single{} }   func GetInstance() *single {     if singleInstance == nil {         log.Fatal("Single Instance is nil")     } else {         fmt.Println("Single Instance already created-2")     }     return singleInstance }

This should be the lazy singleton creation method in go language. If you don't mind the resource occupation caused by creating instances too early, this method is recommended to create singletons.

Via sync Once

sync. The code in once is guaranteed to be executed only once, which can be used to create singletons. The code is as follows:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import (     "fmt"     "sync" )   var once sync.Once   type single struct { }   var singleInstance *single   func GetInstance() *single {     if singleInstance == nil {         once.Do(             func() {                 fmt.Println("Creating Single Instance Now")                 singleInstance = &single{}             })         fmt.Println("Single Instance already created-1")     } else {         fmt.Println("Single Instance already created-2")     }     return singleInstance }

Compared with the method of secondary verification, the code here can be said to be very concise. This is also a singleton creation method that I highly recommend.

The output content is:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Creating Single Instance Now Single Instance already created-1 Single Instance already created-2 Single Instance already created-2 Single Instance already created-1 Single Instance already created-2 Single Instance already created-1 Single Instance already created-1 Single Instance already created-2 Single Instance already created-1 Single Instance already created-2 Single Instance already created-1 Single Instance already created-2 Single Instance already created-2 Single Instance already created-2 Single Instance already created-2 Single Instance already created-2 Single Instance already created-2 Single Instance already created-2 Single Instance already created-2 ...

For a brief description:

  • The output content is similar to the method of secondary verification. There are still multiple lines of "Single Instance already created-1" output, indicating that multiple goroutine s have passed the if verification
  • There is only one line of "Creating Single Instance Now" in the output content, indicating that only one goroutine can create an instance.

Code uploaded to GitHub: zhyea / go-patterns / singleton-pattern

End!

 

Turn https://www.cnblogs.com/amunote/p/15253251.html

Topics: Go Design Pattern