Before diving deep into the actual topic, it’s really important to make yourself familiar with the terminologies below.
What are Design patterns ?
In software engineering design patterns are solutions to general problems that occurred during the development of a software application faced by the majority of developers. The solutions were standardized over a period of trial and errors.
What is Race condition ?
A race condition is a scenario of a program where its behavior depends on relative timing or interleaving of multiple threads or processes. One or more possible outcomes may be undesirable, resulting in a bug.
What is the Singleton Design Pattern?
Singleton pattern restricts instantiation of a class or struct to only one single instance at any point of time. Usually its scope is defined as global to allow access of this instance across your application. This design pattern comes really handy in use cases such as logging, caching, Thread pool, DB connections,etc.
Singleton pattern could be implemented in one of 2 ways i.e Thread Safe and Non Thread Safe. By the way it's suggested to use thread safe.
What is Thread safe?
Thread safety is a mechanism to protect your application from entering into Race condition if it is having a shared state being manipulated by multiple threads at the same time.
What is Non Thread safe?
It does not check the safety of the threads which makes it faster to run but at the same time, it becomes more unstable for multithreaded applications. It works best for either single thread or sequential applications.
First let’s see the Non Thread Safety in action
Non Thread safe implementation in Go
As you can see in the code snippet below, a struct named
SQLConnection and 3 functions named
performSequentialAction are being implemented.
mockConnectionNonThreadSafe function, a
sleep command is added to just mimic the real world scenario( creation of connection object always takes some arbitrary time).
To see non thread safe sequential execution in action, uncomment the
performSequentialAction block in main function and run the snippet using this command.
go run singleton_pattern/non_thread_safe.go
If you get the same output as above, then you have successfully implemented the
non thread safe sequential program. The output clearly shows that sequential execution works absolutely fine, in fact it’s just created a single instance which is kind of a singleton pattern, but performance is a trade off here because this is a step by step execution which means this program can’t take advantage of multiple cores and threads of a modern day cpu.
non thread safe concurrent execution, uncomment the
performConcurrentAction block in main function and run the snippet.
The output will look something similar to this.
If you watch the output closely, you will find that the database instance is continuously modified by every concurrent execution due to some delay in creation of a connection instance ( which is expected in the real-world may be due to network or overload or something else). This is happening because the provided solution is not thread safe, that’s why the connection object is modified by other threads kind of a race condition. In this case we have achieved to successfully utilize the full cpu performance i.e cores and threads, but it’s still far away from the desired output.
Now it’s finally time to resolve this once for all.
Thread Safe implementation in Go
Again mostly similar code 1 struct and 3 functions(1 being
mockConnectionThreadSafe) has been implemented.
As well as a new variable
once of type
Once is a Golang thread safe built-in struct that is used to implement a singleton pattern) has been added. This variable has a method
Once which will handle thread safe execution.
Execute this snippet by uncommenting
performConcurrentAction block in main function.
go run singleton_pattern/thread_safe.go
If you have done everything correctly till now, then you will see the database instance being created only once( unlike connection objects being modified by other concurrent executions in case of non thread safe) even though we are using concurrent executions. The race condition has been handled and it’s working as expected by utilizing the full power of modern day CPU's. Congrats⭐ on top of that it’s absolutely thread safe.
Now you can access the same instance variable wherever you want without any conflicts.
Awesome 🔥, you have successfully completed this tutorial. I would 💝 to hear your feedback and comments on the great things you're gonna build with this. If you are struck somewhere feel free to comment. I am always available.
Please find the complete code at github