Understanding the Singleton Pattern in Go: Eager vs Lazy Implementations
The article explains Go's singleton pattern, detailing eager (init) and lazy implementations using sync.Mutex and sync.Once, provides full code examples, demonstrates that each accessor returns the same instance, and discusses trade‑offs between memory usage and performance.
Singleton Pattern in Go
The singleton pattern restricts a class to a single instance by making the constructor private and exposing a static public accessor. In Go this is expressed by defining a private struct type and providing package‑level functions that return the sole instance.
Three concrete implementations
Eager (init) implementation – the instance is created in an init function, which runs automatically when the package is imported.
Lazy implementation with sync.Mutex – the instance is created on the first call; a mutex protects the double‑checked locking sequence.
Lazy implementation with sync.Once – the instance is created exactly once by calling once.Do.
Code
package singleton
import "sync"
// ----- Lazy implementation (Mutex) -----
var mu sync.Mutex
type lazy struct {
Name string
}
var l *lazy = nil
func GetLazy() *lazy {
if l == nil {
mu.Lock()
if l == nil {
l = new(lazy)
l.Name = "Lazy man"
}
mu.Unlock()
}
return l
}
// ----- Lazy implementation (Once) -----
var once sync.Once
type lazyonce struct {
Name string
}
var lo *lazyonce = nil
func GetOnce() *lazyonce {
once.Do(func() {
lo = &lazyonce{Name: "lazyOnce"}
})
return lo
}
// ----- Eager implementation (init) -----
var h *hungry = nil
type hungry struct {
Name string
}
func init() {
h = new(hungry)
h.Name = "Hungry man"
}
func GetHungry() *hungry {
return h
}Usage demonstration
func main() {
fmt.Println("单例模式")
h1 := singleton.GetHungry()
h2 := singleton.GetHungry()
if h1 == h2 {
fmt.Println("h1==h2")
}
l1 := singleton.GetLazy()
l2 := singleton.GetLazy()
if l1 == l2 {
fmt.Println("l1==l2")
}
lo1 := singleton.GetOnce()
lo2 := singleton.GetOnce()
if lo1 == lo2 {
fmt.Println("lo1==lo2")
}
}Running the program prints:
单例模式
h1==h2
l1==l2
lo1==lo2Key points of each getter
GetLazy() – uses a sync.Mutex to ensure the global variable l is assigned only once.
GetOnce() – relies on sync.Once 's Do method, which guarantees the initialization function runs exactly once, assigning lo a single value.
GetHungry() – returns the instance created by the package‑level init function, which executes before any caller code.
Comparison of lazy vs eager approaches
Lazy (Mutex / Once) – the object is allocated only when the accessor is first invoked, saving memory for rarely used singletons.
Eager (init) – the object is allocated during package import, eliminating synchronization overhead at call time and favoring speed over memory consumption.
The choice between these implementations depends on project requirements: memory‑constrained scenarios may prefer lazy initialization, while performance‑critical code may opt for eager initialization.
Source repository: https://github.com/gofish2020/gopattern/tree/main/creator
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Nullbody Notes
Go backend development, learning open-source project source code together, focusing on simplicity and practicality.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
