Sync.Once vs Singleton Initialisation

I have to get a single instance of DB. For this I wrote the following code -

func getDb() map[string]int {
	if db == nil {
		lock.Lock()
		if db == nil {
			db = map[string]int{}
		}
		lock.Unlock()
	}
	return db
}

There is another way to do this using Sync.Once -

func (o *Once) Do(f func()) {
	if atomic.LoadUint32(&o.done) == 0 {
		o.doSlow(f)
	}
}
func (o *Once) doSlow(f func()) {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

Which one should I use ?
Is the
atomic.LoadUint32(&o.done) == 0 computationally expensive than db == nil ?

the two examples are not doing the same thing. Once is a ready to use solution while the first example is handmade. if have to do a lot of times …maybe better Once

But, the benchmark tool suggests otherwise -

well, if for your use case 130ns are critical … use the first solution, otherwise I prefer to use “ready-to-use” solution; but it’s just my idea

Hi @Harshit_Soni ,

Use sync.Once to create singletons, that is the idiomatic Golang way.

Why do you care how fast is this trivial and rarely used operation ? You usually only need to create a connection to DB only once in your application, why do you care if it takes 10% longer ? If a code is easier to read (maintain) it is usually worth to choose the worse algorithm.

Your benchmark shows only a very small difference, and may even show the opposite if you run it for a longer time. Seems like an easy choice to go for sync.Once

Right. If I’m not mistaken, we are talking about 0.00000013 seconds on an operation that, by definition, is going to be performed only once. Depending on the use case, this might also be a good candidate for an init function. That way you can take concurrency out of the picture completely and this db property is just there when your app starts.

Atomics are low-level memory primitives and are pretty performant based on my usage/understanding of them. I’m wondering if that very slight performance gap is caused by the defer o.m.Unlock()? Also perhaps there’s very slight overhead from having multiple functions / a closure. Either way, it doesn’t matter because I can guarantee you that connecting to your database and authenticating is going to take orders of magnitude longer than any performance hit you might incur by using sync.Once.

1 Like

Thanks a lot everyone,
got my answer. :smile:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.