Golang: Singletons

Piyush Verma
4 min readJun 29, 2016

--

I was recently writing an application in Golang which required some Database interaction. The db library I was using had inbuilt Pooling so I didn’t have to bother about connection recycling and reusing, as long as I could initialise a DbPool and continue to call DbPool.new(). Having a module level Singleton object of DbPool would do this trick. However the problem with Singletons is that, in a multi threaded environment, the initialisation must be protected to prevent re-initialisation. I will discuss a few common ways to achieve this, along-with the shortcomings of each approach.

Module init()

Most common approach I have come across is to define an init() functions in module files. These module level construtors perform operations like DB Pool initialisation or caches. It is guaranteed that this code runs once-and-only-once at startup of your program. Looks good.

I have two problems with this approach:

  1. Import Order: The import order is defined by the order in which the files show up in your code, and unless you rename your files obscurely there is no way to control this sequence.
  2. Implicit Calls: These inits are automatically called at startup and there is no way to invoke it explicitly. This makes it quite a challenge to test such codes. Like if you wanted to test a part of the code which depends on a DB state pre-initialised, you cannot easily mock the connection by seeding that value from within the test suite.

Import Order Problem

Let’s say you have a directory structure that looks something like this:

.
├── abc
│ ├── one.go
│ └── two.go
├── main.go
└── pack
├── one.go
└── two.go

Where one.go looks has the following init method:

func init() {
log.Println("<package_name> - One")
}

And two.go’s init method looks like this:

func init() {
log.Println("<package_name> - Two")
}

And your main.go had a very simple code which looks like this:

package mainimport (
"log"
"./abc"
"./pack"
)
func main() {
log.Println(pack.PackOne, pack.PackTwo, abc.AbcOne, abc.AbcTwo)
}

Output will always be:

2016/06/29 21:34:53 Abc - One
2016/06/29 21:34:53 Abc - Two
2016/06/29 21:34:53 Pack - One
2016/06/29 21:34:53 Pack - Two
2016/06/29 21:34:53 hello world
2016/06/29 21:34:53 1 2 1 2

Since package abc appears ahead of package pack (alphabetically), and both of them are included in the main, there is no way you can alter the init order without renaming the package to something else.
Also, If pack.PackOne had to be seeded with a mock value while testing, it cannot be done because there is no way of invoking the init method explicitly. And while testing, Database connectors is something that you more-often-than-not have to mock.

sync.RWMutex

Alternate approach to do this is to use a Module level cache variable with embedded Read-Write Mutex to ensure synchronisation across multiple go-routines.

An explicit method can then be used to acquire a ReadWrite Lock to check and return if the value had already been initialised, or initialise it with a value and return that otherwise.

A sample code for such an approach would look like this:

On carefully examining the output of this code you will observe a problem that the code tries to attain locks even after the first initialisation is complete.

2016/06/29 21:22:57 lock 5
2016/06/29 21:22:57 Initializing GetInt
2016/06/29 21:22:57 lock freed 5
2016/06/29 21:22:57 &{1}
2016/06/29 21:22:57 lock 3
2016/06/29 21:22:57 lock freed 3
2016/06/29 21:22:57 &{1}
2016/06/29 21:22:57 lock 1
2016/06/29 21:22:57 lock freed 1
2016/06/29 21:22:57 &{1}
2016/06/29 21:22:57 lock 2
2016/06/29 21:22:57 lock freed 2
2016/06/29 21:22:57 &{1}
2016/06/29 21:22:57 lock 0
2016/06/29 21:22:57 lock freed 0
2016/06/29 21:22:57 &{1}
2016/06/29 21:22:57 lock 4
2016/06/29 21:22:57 lock freed 4
2016/06/29 21:22:57 &{1}

After 5 was initialised; 1, 2, 3, and 4 should have been free to run in Parallel. Since the access to the cached value is bound by a ReadWrite Lock and only one goroutine would have that at a time, they pretty much execute in a sequence.

There should be a way to better to tackle this.

sync.Once

By Definition: Singleton is a design pattern that restricts the instantiation to one object. It would be lot more efficient if there was a way to lock JUST the first initialisation. Thereafter, any piece of code should be free to access the value without having to bother aout Locking and inevitably Blocking other resources.

sync.Once allows you to do exactly that, where Once is an object that will perform exactly one action.
You can read more about the documetation here: https://golang.org/pkg/sync/#Once

The same code, as demonstrated in the last method, when moved to sync.Once pattern will look like this:

While the output of this code will be:

2016/06/29 21:26:42 No lock 5
2016/06/29 21:26:42 No lock 0
2016/06/29 21:26:42 No lock 2
2016/06/29 21:26:42 No lock 4
2016/06/29 21:26:42 No lock 3
2016/06/29 21:26:42 Initializing GetInt
2016/06/29 21:26:42 No lock return 5
2016/06/29 21:26:42 &{1}
2016/06/29 21:26:42 No lock return 0
2016/06/29 21:26:42 &{1}
2016/06/29 21:26:42 No lock return 2
2016/06/29 21:26:42 No lock 1
2016/06/29 21:26:42 No lock return 1
2016/06/29 21:26:42 &{1}
2016/06/29 21:26:42 No lock return 4
2016/06/29 21:26:42 &{1}
2016/06/29 21:26:42 &{1}
2016/06/29 21:26:42 No lock return 3
2016/06/29 21:26:42 &{1}

Do observe that after the first initialisation of 5, goroutines do not block each other and pretty much run at random. This by-passes the locking and still provide you the flexibility of being able to invoke it explicitly. The only down side of this method is that you would need a separate Once object for each such cached variable in your code. It also requires a promise that the value is not going to change through the lifecycle of the code.

--

--

Piyush Verma

CTO/Founder @last9inc | Startup magnate (2x fail, 1x exit) | English Breakfast Tea, Hot