Design Patterns in Go: Singleton, a unique way of creating objects in a threadsafe way

Introduction

The singleton pattern restricts the instantiation of a class to a single instance. The singleton pattern makes it possible to ensure that:

  1. The creation of a class is controlled (in some languages like C# or Java this is done by making the constructors private)
  2. The one instance we have is easily accessible
  3. And the singleton pattern helps us to ensure we only have at most one instance of a class.

One of the main uses of the singleton pattern is logging, as all clients who wish to log need a single point of entrance.

The UML diagram of this pattern is very simple:

A Singleton has

  • a private constructor, or a static constructor. In the constructor, a check is done to see if there is an instance of the Singleton. If there is, that instance is returned, if not a new one is made, stored and returned
  • It contains an instance of itself, in some languages this is stored in a private field.
  • The getInstance() method is used to get the single instance. If there is no instance yet, the constructor is called and a new instance is made.

Implementation in Go

Open your terminal or commandline in an empty directory and type:

go mod init github.com/singleton

Then open your favourite IDE and add a main.go file. Add these preliminaries:

package main

import (
	"fmt"
	"sync"
)

The Singleton for simplicity’s sake is very simple and just holds an int:

type Singleton struct {
	data int
}

Next we come to some more complex code:

var (
	singletonInstance *Singleton
	once              sync.Once
)

func GetInstance() *Singleton {
	once.Do(func() {
		singletonInstance = &Singleton{data: 42}
	})
	return singletonInstance
}

A short breakdown:

  1. The singletonInstance variable holds a reference to a Singleton struct
  2. once is used to make sure that we only call the struct constructor once. If you want to know more, have a look at the docs.
  3. In the get instance, we make sure, the initialization code is called only once, even in the presence of multiple threads, and we return the instance. By using the sync.once struct, we make sure our Singleton is threadsafe. Not doing that can lead to all sorts of trouble. The only unsafe part would be setting the data, but I will come back to that in a later blog.

Now we can start testing it:

func main() {
	instanceA := GetInstance()
	fmt.Println("InstanceA:", instanceA)
	instanceA.data = 43

	instanceB := GetInstance()
	fmt.Println("InstanceB:", instanceB)
	fmt.Println("InstanceA:", instanceA)

}

Line by line:

  1. We acquire an instance of our Singleton struct and we print out the value.
  2. We set the value of the data to 43
  3. We acquire an instance of our Singleton and assign to a different variable. This should of course be the same instance.
  4. We print out the value of the new variable and we find the new value, so it is the same instance
  5. And just to make sure we print out the first instance

Conclusion

The Singleton pattern could be said to be of limited use. However, in some cases, like logging, there are no good alternatives. The main things is to make sure your singleton is threadsafe, since it could be accessed from different threads.

The implementation in Go was very easy, and quite elegant. The sync package makes it quite easy to make this pattern threadsafe.

Leave a Reply

Your email address will not be published. Required fields are marked *