Thread Safe Singleton in C++
Filed in: C++
In this article we will talk about creating a thread safe Singleton in C++. If you are interested in Java Singleton creation please follow this link here.
First let us talk about the Double Checked Locking pattern and why at first it looks good but has a serious flaw. Firstly the code
static std::mutex myMutex;
class DoubleChecked{
public:
static DoubleChecked& getInstance(){
if(!instance){
std::lock_guard<std::mutex> myLock(myMutex);
if(!instance){
//This next line has multiple non Atomic steps which could race by leaking the instance early
//Need to alloc memory
//need to actually create the object
//need to assign to instance
instance=new DoubleChecked();
}
}
return *instance;
}
private:
static DoubleChecked* instance;
//cons
DoubleChecked()=default;
~DoubleChecked()=default;
DoubleChecked(const DoubleChecked&)= delete;
DoubleChecked& operator=(const DoubleChecked&)=delete;
};
DoubleChecked* DoubleChecked::instance=nullptr;
Now while this looks like it may work the problem is a problem with the instance= new DoubleChecked(); line. This is actually 3 steps
1. allocate memory - which can fail
2. Actually create the object meaning objects can be half formed if this goes wrong.
3. Now we assign it to the instance.
So while this may work it is not guaranteed to work all the time. Hence there are better ways of creating a singleton and guarantee the formation.
This is the MyersSingleton which is guaranteed because of static initialization in Block scope is guaranteed by C++
class MyersSigleton{
public:
static MyersSigleton& getInstance(){
static MyersSigleton instance;
return instance;
}
std::chrono::system_clock::time_point getValue(){
return now;
}
private:
std::chrono::system_clock::time_point now= std::chrono::system_clock::now();
MyersSigleton()=default;
~MyersSigleton()=default;
MyersSigleton(const MyersSigleton&)= delete;
MyersSigleton& operator=(const MyersSigleton&)=delete;
};
The other way to create a Singleton is to use the std library to guarantee that a piece of code will be run once and only once. This is the recommended practice for creating a Singleton in C++.
class OnceFlagSigleton{
public:
static OnceFlagSigleton* getInstance(){
std::call_once(initInstanceFlag,OnceFlagSigleton::initSingleton);
return instance;
}
static void initSingleton(){
instance= new OnceFlagSigleton();
}
private:
static std::once_flag initInstanceFlag;
static OnceFlagSigleton* instance;
OnceFlagSigleton()=default;
~OnceFlagSigleton()=default;
OnceFlagSigleton(const OnceFlagSigleton&)= delete;
OnceFlagSigleton& operator=(const OnceFlagSigleton&)=delete;
};
OnceFlagSigleton* OnceFlagSigleton::instance= nullptr;
std::once_flag OnceFlagSigleton::initInstanceFlag;
The reason this works is because we have used a std::once_flag and the static that creates the Singleton the getInstance uses std::call_once using this instance flag. If the flag is used then the instance is never again created.
C++ Asynch future promise package_task and shared_future
Blocking Queue in C++ using condition variable
Creating C++ threads
Low Latency Java using CAS and LongAdder
Naive Bayes classification AI algorithm
K-Means Clustering AI algorithm
Equity Derivatives tutorial
Fixed Income tutorial
Java
python
Scala
Investment Banking tutorials
HOME

First let us talk about the Double Checked Locking pattern and why at first it looks good but has a serious flaw. Firstly the code
//Double Checked locking - Best avoided
static std::mutex myMutex;
class DoubleChecked{
public:
static DoubleChecked& getInstance(){
if(!instance){
std::lock_guard<std::mutex> myLock(myMutex);
if(!instance){
//This next line has multiple non Atomic steps which could race by leaking the instance early
//Need to alloc memory
//need to actually create the object
//need to assign to instance
instance=new DoubleChecked();
}
}
return *instance;
}
private:
static DoubleChecked* instance;
//cons
DoubleChecked()=default;
~DoubleChecked()=default;
DoubleChecked(const DoubleChecked&)= delete;
DoubleChecked& operator=(const DoubleChecked&)=delete;
};
DoubleChecked* DoubleChecked::instance=nullptr;
Now while this looks like it may work the problem is a problem with the instance= new DoubleChecked(); line. This is actually 3 steps
1. allocate memory - which can fail
2. Actually create the object meaning objects can be half formed if this goes wrong.
3. Now we assign it to the instance.
So while this may work it is not guaranteed to work all the time. Hence there are better ways of creating a singleton and guarantee the formation.
This is the MyersSingleton which is guaranteed because of static initialization in Block scope is guaranteed by C++
//Myers Singleton or Locking based on the guarantee of Static Variables in Block Scope
class MyersSigleton{
public:
static MyersSigleton& getInstance(){
static MyersSigleton instance;
return instance;
}
std::chrono::system_clock::time_point getValue(){
return now;
}
private:
std::chrono::system_clock::time_point now= std::chrono::system_clock::now();
MyersSigleton()=default;
~MyersSigleton()=default;
MyersSigleton(const MyersSigleton&)= delete;
MyersSigleton& operator=(const MyersSigleton&)=delete;
};
The other way to create a Singleton is to use the std library to guarantee that a piece of code will be run once and only once. This is the recommended practice for creating a Singleton in C++.
//Singleton with std::once_flag
class OnceFlagSigleton{
public:
static OnceFlagSigleton* getInstance(){
std::call_once(initInstanceFlag,OnceFlagSigleton::initSingleton);
return instance;
}
static void initSingleton(){
instance= new OnceFlagSigleton();
}
private:
static std::once_flag initInstanceFlag;
static OnceFlagSigleton* instance;
OnceFlagSigleton()=default;
~OnceFlagSigleton()=default;
OnceFlagSigleton(const OnceFlagSigleton&)= delete;
OnceFlagSigleton& operator=(const OnceFlagSigleton&)=delete;
};
OnceFlagSigleton* OnceFlagSigleton::instance= nullptr;
std::once_flag OnceFlagSigleton::initInstanceFlag;
The reason this works is because we have used a std::once_flag and the static that creates the Singleton the getInstance uses std::call_once using this instance flag. If the flag is used then the instance is never again created.
People who enjoyed this article also enjoyed the following:
C++ Asynch future promise package_task and shared_future
Blocking Queue in C++ using condition variable
Creating C++ threads
Low Latency Java using CAS and LongAdder
Naive Bayes classification AI algorithm
K-Means Clustering AI algorithm
Equity Derivatives tutorial
Fixed Income tutorial
And the following Trails:
C++Java
python
Scala
Investment Banking tutorials
HOME
