Skip to content

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:

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.