Go singleton implementation scheme

Everyone knows the singleton pattern well, and it is defined as follows: a class is only allowed to create only one object (or instance), then this class is a singleton class. This design pattern is called the singleton design pattern, or the singleton pattern for short.

Although the singleton mode is relatively simple to understand, there are many details that need to be considered when it is actually implemented. The general considerations are as follows:

  1. The constructor needs to have private access rights, so as to avoid external creation of instances through new

  2. Consider thread safety issues when creating objects

  3. Consider whether to support lazy loading

  4. Consider whether the performance of getInstance() is high (whether to lock or not)

Code

The grammar is different, the degree of attention to these points is also different. For the Go language, here is a way of writing, using sync.Once.Do. The purpose of this function is to execute it only once.

So we can write:

type Single struct {
    
    

}
var (
   once sync.Once
   single     *Single
)


func GetSingleInstance() *Single {
    
    
   once.Do(func() {
    
    
      single = &Single{
    
    }
   })
   return single
}

No matter how many requests, there will only be one instance of Single.

test

Now let’s think about a scenario. If 100 requests suddenly request the GetSingleInstance interface at the same time, will these requests wait for Do to execute or just return single regardless of Do?

Theoretically, you need to wait for the Do execution to complete, otherwise the returned single is empty, which will cause a serious error. Although you think so, let's do a test.

package main

import (
   "fmt"
   "strconv"
   "sync"
   "time"
)

func main() {
    
    
   var once sync.Once
   onceBody := func() {
    
    
      fmt.Println("Only once start")
      time.Sleep(time.Second * 5)
      fmt.Println("Only once end")
   }

   for i := 0; i < 5; i++ {
    
    
      j := i
      go func(int) {
    
    
         fmt.Println(j)
         once.Do(onceBody)
         fmt.Println("lll" + strconv.Itoa(j))
      }(j)

   }
   fmt.Println("finished")
   time.Sleep(time.Second * 10)
}

The test plan is very simple, start 5 goroutines, call Do at the same time, onceBody sets sleep for 5 seconds, just check the output, you can judge whether it will block.

The execution results are as follows:

➜ myproject go run main.go
finished
0
Only once start
2
4
3
1
Only once end
lll2
lll4
lll0
lll1
lll3

It can be seen that all goroutines will be output only after Do is executed, which proves that Do will be called, but only one will be actually executed. Before the real execution is completed, other goroutines will be blocked.

In fact, there is a hidden risk. If the function executed by Do is time-consuming, it will cause a large number of goroutines to accumulate. This needs to be considered when programming.

Implementation

How is the Do function implemented? Let's take a look at the source code:

func (o *Once) Do(f func()) {
    
    
   if atomic.LoadUint32(&o.done) == 0 {
    
    
      // Outlined slow-path to allow inlining of the fast-path.
      o.doSlow(f)
   }
}
func (o *Once) doSlow(f func()) {
    
    
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
    
    
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

When multiple goroutines check the done value is 0, enter doSlow, only one goroutine will get the lock, and other goroutines will not block.

The practical ones are more conventional technologies, mainly mutual exclusion locks, semaphores, and defer, but the design is still very clever. This is also an advantage of Go. The use of locks to resolve conflicts is fast, safe and convenient, but performance issues need to be considered.

At last

If you like my article, you can follow my official account (Programmer Mala Tang)

My personal blog is: https://shidawuhen.github.io/

Review of previous articles:

technology

  1. Go design pattern (4)-code writing
  2. Go design pattern (3)-design principles
  3. Go design pattern (2)-object-oriented analysis and design
  4. General issues of payment access
  5. HTTP2.0 basic tutorial
  6. Go design pattern (1)-grammar
  7. MySQL Development Specification
  8. HTTPS configuration combat
  9. Principles of Go Channel Implementation
  10. Go timer implementation principle
  11. HTTPS connection process
  12. Current limit realization 2
  13. Spike system
  14. Distributed system and consensus protocol
  15. Service framework and registry of microservices
  16. Beego framework usage
  17. Talking about microservices
  18. TCP performance optimization
  19. Current limit realization 1
  20. Redis implements distributed locks
  21. Golang source code bug tracking
  22. The realization principle of transaction atomicity, consistency and durability
  23. Detailed CDN request process
  24. Common caching techniques
  25. How to efficiently connect with third-party payment
  26. Gin framework concise version
  27. A brief analysis of InnoDB locks and transactions
  28. Algorithm summary

study notes

  1. in principle
  2. Zi Zhi Tong Jian
  3. Agile revolution
  4. How to exercise your memory
  5. Simple logic-after reading
  6. Hot air-after reading
  7. The Analects-Thoughts after Reading
  8. Sun Tzu's Art of War-Thoughts after reading

Thinking

  1. Against liberalism
  2. Practice theory
  3. Evaluate one's own standards
  4. Server team holiday duty plan
  5. Project process management
  6. Some views on project management
  7. Some thoughts on product managers
  8. Thoughts on the career development of programmers
  9. Thinking about code review
  10. Markdown editor recommendation-typora

Guess you like

Origin blog.csdn.net/shida219/article/details/114818645