Singleton Design Pattern on Golang
The Singleton design pattern ensures that a class has only one instance and provides a global point of access to that instance.
In Go, we typically implement this using a combination of sync.Once and package-level variables.
Golang Basic Implementation with sync.Once
sync.Once
ensures that instance is initialized only once, even in concurrent environments. The use of sync.Once also ensures that the singleton instance is created in a thread-safe manner.
package main
import (
"fmt"
"sync"
)
// Singleton struct
type Singleton struct {
Value int
}
var instance *Singleton
var once sync.Once
// GetInstance returns the singleton instance
func GetInstance() *Singleton {
once.Do(func() {
instance = &Singleton{Value: 42} // Initialize with some default value
})
return instance
}
func main() {
s1 := GetInstance()
s2 := GetInstance()
fmt.Println(s1 == s2) // true (both variables point to the same instance)
fmt.Println(s1.Value) // 42
}
Thread-Safe Singleton with Lazy Initialization
If we want lazy initialization but without sync.Once, we can use a sync.Mutex
to protect the initialization code. This is known as the double-checked locking pattern.
This pattern is not recommended in Go, as it can lead to subtle bugs.
sync.Once
is usually the preferred approach.
package main
import (
"fmt"
"sync"
)
type Singleton struct {
Value int
}
var instance *Singleton
var mutex = &sync.Mutex{}
func GetInstance() *Singleton {
if instance == nil {
mutex.Lock()
defer mutex.Unlock()
if instance == nil { // Double-check locking
instance = &Singleton{Value: 100}
}
}
return instance
}
func main() {
s1 := GetInstance()
s2 := GetInstance()
fmt.Println(s1 == s2) // true
fmt.Println(s1.Value) // 100
}
Real-World Example: Database Connection Singleton in Go
In many applications, a database connection is a good candidate for the Singleton pattern because:
- We don’t want to create multiple database connections.
- Managing a single connection pool improves performance.
package main
import (
"database/sql"
"fmt"
"log"
"sync"
_ "github.com/lib/pq" // Import PostgreSQL driver
)
type Database struct {
conn *sql.DB
}
var instance *Database
var once sync.Once
// GetDBInstance ensures only one database connection is created
func GetDBInstance() *Database {
once.Do(func() {
fmt.Println("Initializing database connection...")
db, err := sql.Open("postgres", "user=youruser password=yourpass dbname=yourdb sslmode=disable")
if err != nil {
log.Fatal(err)
}
instance = &Database{conn: db}
})
return instance
}
func main() {
db1 := GetDBInstance()
db2 := GetDBInstance()
fmt.Println(db1 == db2) // true (same instance)
// Example query
var now string
err := db1.conn.QueryRow("SELECT NOW()").Scan(&now)
if err != nil {
log.Fatal(err)
}
fmt.Println("Current time from DB:", now)
}
In this example, we use the database/sql
package to create a PostgreSQL connection. The GetDBInstance
function ensures that only one connection is created and shared across the application.
This pattern can be extended to other resources that need to be shared across the application, such as configuration settings, caches, or loggers.
Conclusion
The Singleton pattern is a useful design pattern when you want to ensure that a class has only one instance and provide a global point of access to that instance. In Go, we can implement the Singleton pattern using sync.Once
for thread-safe initialization.