Singleton Design Pattern Description
Explanation of old method (double check thread safe method) in detail:
Approach 1 (Using single if condition) for Singleton Design Pattern:
This approach is not safe for Multi-threaded program, We will face race condition and I will explain the problem in below example:
We have two thread 1 and thread 2.
SingletonLogger *SingletonLogger::GetInstance() { if (m_pinstance == nullptr) { 1. Thread 1 reached here and Context switching happened (OS scheduler give control to other thread (thread 2) 2. Thread 2 also reached here, will create the object using new operator. 3. Context switching happened again and thread 1 got CPU. 4. Thread 1 will create the object using new operator. 5. Now we have 2 objects and this is not expected from singleton class. m_pinstance = new SingletonLogger(); } return m_pinstance; }
Singleton Approach 2 (first lock then if condition):
This approach is safe for Multi-threaded program, but not optimised one. I will explain the problem in below example:
On every call(GetInstance()) we are acquiring the lock and checking the condition, assume we are calling 100 times then 100 times we are acquiring the lock and this is expensive operation so this approach is NOT optimised one.
SingletonLogger *SingletonLogger::GetInstance() { std::lock_guard<std::mutex> lock(m_mutex); if (m_pinstance == nullptr) { m_pinstance = new SingletonLogger(); } return m_pinstance; }
Design PatternĀ Singleton Approach 3 (First if condition then lock):
This approach is not safe for Multi-threaded program, We will face race condition and I will explain the problem in below example:
We have two thread 1 and thread 2.
SingletonLogger *SingletonLogger::GetInstance() { if (m_pinstance == nullptr) { 1. Thread 1 reached here and Context switching happened (OS scheduler give control to other thread (thread 2) 2. Thread 2 also reached and get the lock and will create the object. 3. Context switching happened again and thread 1 got CPU. 4. Thread 1 will get the lock and create the object using new operator. 5. Now we have 2 objects and this is not expected from singleton class. std::lock_guard<std::mutex> lock(m_mutex); m_pinstance = new SingletonLogger(); } return m_pinstance; }
Approach 4 (First if condition then lock then if condition again):
This approach is safe for Multi-threaded program and its optimised one. I will explain the problem in below example:
After creation of first object, On every call(GetInstance()) we are checking the condition not acquiring the lock so this approach is optimised one.
SingletonLogger *SingletonLogger::GetInstance() { if (m_pinstance == nullptr) { std::lock_guard<std::mutex> lock(m_mutex); if (m_pinstance == nullptr) { m_pinstance = new SingletonLogger(); } } return m_pinstance; }
source code with Example:
/* * File:singletonLogger.cpp * This file is described the Singleton Design Pattern with help of example * Singleton Design pattern:Ensure ONLY one object will be created. * Singleton Design pattern:Provide Globall access to it. * Author: Aplha * Date: 02 Sep 2020 */ ///////////////////////////////////Header files//////////////////////////////////// #include <iostream> #include <mutex> #include <thread> //////////////////////////////////Singleton Start////////////////////////////////////// class SingletonLogger { private: static std::mutex m_mutex; static SingletonLogger* m_pinstance; //Singleton's constructor should be private to prevent creation of direct object. SingletonLogger(){} ~SingletonLogger() {} SingletonLogger(const SingletonLogger &other) = delete; // Disallow copying void operator=(const SingletonLogger &) = delete; // Disallow copying public: static SingletonLogger *GetInstance(); void Print(const std::string str); }; // Static variale should be defined outside the class. SingletonLogger* SingletonLogger::m_pinstance{nullptr}; std::mutex SingletonLogger::m_mutex; //Get instance with Double Checked Locking Pattern //You can see two if conditions are there with one lock SingletonLogger *SingletonLogger::GetInstance() { if (m_pinstance == nullptr) { std::lock_guard<std::mutex> lock(m_mutex); if (m_pinstance == nullptr) { m_pinstance = new SingletonLogger(); } } return m_pinstance; } //Print Function of Logger, this is basic function to explain the use of singleton Pattern // we can add according to our requirement. void SingletonLogger::Print(const std::string str) { std::lock_guard<std::mutex> lock(m_mutex); std::cout<<"object Address:"<<m_pinstance<<" value:"<<str<<std::endl; } ///////////////////////////////////////////////Singleton END//////////////////////////////// void ThreadOne(){ // Thread One std::this_thread::sleep_for(std::chrono::milliseconds(1000)); SingletonLogger::GetInstance()->Print("Thread ONE is executing"); } void ThreadTwo(){ // Thread Two std::this_thread::sleep_for(std::chrono::milliseconds(1000)); SingletonLogger::GetInstance()->Print("Thread TWO is executing"); } int main() { std::thread t1(ThreadOne); std::thread t2(ThreadTwo); //waiting SingletonLogger::GetInstance()->Print("Parent process is Waiting for threads"); t1.join(); t2.join(); SingletonLogger::GetInstance()->Print("Parent process please check the address of singleton object it must be same"); SingletonLogger::GetInstance()->Print("Parent process saying BYE BYE"); return 0; }
Compilation command:
g++ -pthread -std=c++11 singletonLogger.cpp
Output:
/DesignPattern$ ./a.out object Address:0x1ed8ef0 value:Parent process is Waiting for threads object Address:0x1ed8ef0 value:Thread ONE is executing object Address:0x1ed8ef0 value:Thread TWO is executing object Address:0x1ed8ef0 value:Parent process please check the address of singleton object it must be same object Address:0x1ed8ef0 value:Parent process saying BYE BYE