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:
-
The constructor needs to have private access rights, so as to avoid external creation of instances through new
-
Consider thread safety issues when creating objects
-
Consider whether to support lazy loading
-
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
- Go design pattern (4)-code writing
- Go design pattern (3)-design principles
- Go design pattern (2)-object-oriented analysis and design
- General issues of payment access
- HTTP2.0 basic tutorial
- Go design pattern (1)-grammar
- MySQL Development Specification
- HTTPS configuration combat
- Principles of Go Channel Implementation
- Go timer implementation principle
- HTTPS connection process
- Current limit realization 2
- Spike system
- Distributed system and consensus protocol
- Service framework and registry of microservices
- Beego framework usage
- Talking about microservices
- TCP performance optimization
- Current limit realization 1
- Redis implements distributed locks
- Golang source code bug tracking
- The realization principle of transaction atomicity, consistency and durability
- Detailed CDN request process
- Common caching techniques
- How to efficiently connect with third-party payment
- Gin framework concise version
- A brief analysis of InnoDB locks and transactions
- Algorithm summary
study notes
- in principle
- Zi Zhi Tong Jian
- Agile revolution
- How to exercise your memory
- Simple logic-after reading
- Hot air-after reading
- The Analects-Thoughts after Reading
- Sun Tzu's Art of War-Thoughts after reading
Thinking
- Against liberalism
- Practice theory
- Evaluate one's own standards
- Server team holiday duty plan
- Project process management
- Some views on project management
- Some thoughts on product managers
- Thoughts on the career development of programmers
- Thinking about code review
- Markdown editor recommendation-typora