Programming

How to Implement Golang’s Singleton Design Pattern

Basic Definition

Singleton is a software design standard. This standard guarantees the existence of only one instance of a class, while maintaining a global point of access to its objects.

As per the quote above, Singleton is a software design standard that guarantees the existence of only one instance of a class, while maintaining a global point of access to its object. Singleton is a design pattern that limits instantiation to an object, we must ensure that this only happens once. Now you’re getting the picture, right?

Usually the implementation that we often use in software is when we initialize the connection to the database. So that when we do a query, we don’t initialize the connection again, we need to use Singleton (one connection) only to better utilize the connection resources to the database. Other implementations for singleton include:

  • Instance DB: we only want to create one instance of the DB object and that one can be used throughout the program.
  • Instance Log: similarly, we only initialize the log DB instance once and it can be used throughout the program.
  • Instance External Call: suppose we initialize the HTTP connection for connection to external APIs, gRPC and others.
  • Instance Connect Cache: creates a single cache instance so that there is no need to initialize the cache again throughout the program.

Unlike global variables, Singleton guarantees that there is only one instance of a class. Nothing, except for the Singleton class itself, can replace the cached instance. Note that you can always customize this restriction and allow the creation of any number of Singleton instances.

Essentially Singleton is a way to use global variables. We know how dangerous the use of global variables can be, our code is vulnerable to global variable access or any part of our system can change their values. So when we try to debug the program, it will not be an easy task to find out which code path leads to the current state, which is why not consider Singleton as Anti Pattern, but a way to protect global variables.

However, the problem with Singleton occurs if in a multi-threaded environment, initialization must be protected to avoid reboots, atomically. Singleton is a feature derived from the object-oriented paradigm, so how do we implement Golang if it has no OO support? To answer this question we must understand that Object Oriented Programming is a concept and can be implemented in any programming language even if it is another paradigm. It is clear that the level of abstraction and difficulty of being a difficult task greatly increases the level of code complexity depending on the language.

Advantages and Disadvantages of Singleton

AdvantagesDisadvantages
Has only one instance.Needs special treatment if conditioned multithread
Create global function accessDifficulty when we create unit tests when the instance is private
Object initialization for the first time only

Basic Singleton Golang

The solution presented below would be ideal if our application was synchronous, and the problem would be solved with the code below, but since our goal is to implement using competition, the solution below is far from ideal. Let’s examine the code below and start the possible implementations:

Below is an example of a basic implementation of an ideally synchronous program using the Singleton pattern. Let’s see the code below how it is implemented:

The above implementation is a simple implementation of Singleton in Golang and we run it with * goroutine* simultaneously. The problem will be seen when gorouting does the first check and all will initialize the instance and will replace each other so there is no guarantee that this instance is returned according to our expectations. Unfortunately, this approach is highly error-prone and there is a high probability that values will vary. This is not good when debugging and it becomes very difficult to detect bugs, because when debugging nothing goes wrong due to runtime breaks. This implementation is not good because it is potentially Not Thread Safe.

Singleton using Locks with Mutex

The code below is a poor implementation of Singleton to try to solve the Thread Safe problem. The code below can solve the problem but it can still be broken.

The code below is a poor solution to try to solve the “Thread Safe” problem. Actually, it solves the “Thread Safe” problem, but creates another potentially serious problem. It introduces a hold in the goroutine that executes aggressive blocking of the entire function, let’s examine the code below: so another potential problem. Here is the code for approach to avoid Thread Safe problem.

The “Thread Safe” issue has been resolved with the above implementation using sync.Mutex where Lockdown occurs before creating the Singleton instance. The big problem with this approach is excessive blocking even if there is no need to do so if the instance has already been created and should return a single instance. If our program is written to be highly concurrent, this could result in a bottleneck, as only one * goroutine* routine can get a single instance at a time, making this solution very slow. Let’s examine another solution, as this is not our best approach above.

Singleton using Check-Lock-Check

One way to improve and ensure minimum locking and stay safe for goroutines is to use a pattern called Check-Lock-Check when acquiring locks. But you must use Mutex with atomic so that it is not “tread safe”. Check the code below:

Singleton using sync.Once

We have Once type in sync library, given that this library in Golang is very useful and we should use it as best as possible, sync.Once object will do initialization exactly once and no more, it is the best and powerful implementation.

With this approach, our code in addition to being cleaner is much better, the sync.Once function guarantees the uniqueness of the instance, it can now have 100 goroutines or more according to its needs, putting them in competition instead we will have Thread Safe issues or aggressive checks. A simple and safe way to write Golang code to implement the Singleton Pattern.

Conclusion

The ideal is undoubtedly the use of sync after it guarantees a unique, single initialization and also Thread Safe which guarantees that “Race Conditions” do not occur, It allows functions to be executed only after Golang automates all. Golang really becomes more powerful in this approach, making it easy to implement and understand. When we talk about competition, our whole way of thinking and coding solutions using Golang changes drastically. There are several scenarios that we need to implement standards and practices in our projects to fully utilize the power that Golang has to offer.

Hopefully, this article will help clarify some doubts about using Singleton Pattern, using global variables, goroutines, sync.Once, Init, Mutex, and some other important technical points covered in this article.

References

comments powered by Disqus