Building a Thread-Safe Singleton Pattern in C++ for Scalable Systems

In this tutorial, we will learn to create a Thread Safe Singleton Pattern for a scalable system. This is a very simple but important concept for any developer working on or creating an application using C++.

 

What is the Singleton Pattern? Why Singleton is Useful in Scalable Systems?

A Singleton pattern ensures that a class has only one instance. Different parts of the system can access the single instance through a globally accessible method provided by the class. Therefore, this is commonly used in applications such as configuration managers, loggers, caching systems, or database connection pools. In modern applications, improper handling of a singleton pattern when using multi-threading can result in unpredictable behaviour. Therefore, thread-safe design must be used when dealing with such scenarios.

Basic Singleton Implementation

Code Snippet
#include<iostream>
using namespace std;

class Singleton{
private:

    static Singleton* s_instance;

    Singleton(){
        cout<<"Constructor called."<<endl;
    }

    ~Singleton(){
        cout<<"Destructor called."<<endl;
    }


public:

    static Singleton* getInstance(){
        if(s_instance == nullptr){
            s_instance = new Singleton();
        }
        return s_instance;
    }

};

Singleton* Singleton::s_instance = nullptr;


int main(){

    Singleton* one = Singleton::getInstance();
    Singleton* two = Singleton::getInstance();
    return 0;
}
Output
Constructor called.
Explaination
Singleton class
  • Static private member ‘‘s_instance‘: a static variable is declared. We do not want this variable to be accessed by anyone directly outside the class.
  • Private constructor: It prevents others from creating objects, which is important because we want to use only one instance through the class itself.
  • Static function ‘getInstance()‘: initialises and returns the static member variable of the class.
  • Singleton* Singleton::s_instance = nullptr : In c++, static members are initialised outside the class. It is pointing to a null pointer.
Main function

The first call creates the instance (“constructor called” is printed). The second call reuses the same instance. So only one constructor call happens and the instances is not created twice.

This works perfectly fine for a single-threaded application.

Unsafe multi-thread Singleton Implementation

Code Snippet
#include<iostream>
#include <thread>

using namespace std;

class Singleton{
private:

    static Singleton* s_instance;

    Singleton(){
        cout<<"Constructor called."<<endl;
    }

    ~Singleton(){
        cout<<"Destructor called."<<endl;
    }


public:

    static Singleton* getInstance(){
        if(s_instance == nullptr){
            s_instance = new Singleton();
        }
        return s_instance;
    }

};

Singleton* Singleton::s_instance = nullptr;

void createInstance() {
    Singleton* instance = Singleton::getInstance();
}

int main() {
    std::thread t1(createInstance);
    std::thread t2(createInstance);

    t1.join();
    t2.join();

    return 0;
}

Output

Constructor called.
Constructor called.
Changes to the code
  • Added library ‘thread’. (line 2)
  • Added function createInstance(): Added a new separate function to wrap the call to singleton::getInstance(). (line 33)
  • Main function: Instead of trying to initialise two instances, we have started two threads that do it for us.(lines 38, 39, 41 and 42)
Explanation

The “constructor called” was printed two times. This happened because two threads were created, both running the same function simultaneously, which called the getInstance() function of the Singleton class. This resulted in the creation of the instance twice. To solve this problem, we will now look at thread-safe techniques.

Thread-Safe Singleton Techniques

1) Using std::mutex

Code Snippet

#include<iostream>
#include <thread>
#include<mutex>

using namespace std;

class Singleton{
private:

    static Singleton* s_instance;
    static mutex mtx;

    Singleton(){
        cout<<"Constructor called."<<endl;
    }

    ~Singleton(){
        cout<<"Destructor called."<<endl;
    }


public:

    static Singleton* getInstance(){
        lock_guard<mutex> lock(mtx);
        if(s_instance == nullptr){
            s_instance = new Singleton();
        }
        return s_instance;
    }

};

Singleton* Singleton::s_instance = nullptr;
mutex Singleton::mtx;

void createInstance() {
    Singleton* instance = Singleton::getInstance();
}

int main() {
    std::thread t1(createInstance);
    std::thread t2(createInstance);

    t1.join();
    t2.join();

    return 0;
}
Output
Constructor called.
Changes to the code
  • Added library ‘mutex’.(line 3)
  • Added a static mutex member inside the class ‘mtx’. (line 11)
  • Added lock_guard<mutex> lock(mtx);.  (line 25)
  • Added mutex Singleton::mtx;. (line 35)
Explanation

Mutex prevents multiple threads from entering the ‘critical section’ (part after lock_guard is used, line 25) at the same time. Therefore, when the mutex allowed one thread to execute the getInstance() function, it resulted in the creation of an instance (“constructor called” was printed). Then, when the second thread got access to the critical part of the function, it did not create the instance again but returned the already initialised instance.

Problem:

This method locks the mutex on every getInsatance() call. This results in delays and reduced performance.

Therefore, this method should only be used when performance is not a big factor.

2) Using Meyers’ Singleton

Code Snippet

#include<iostream>
#include <thread>

using namespace std;

class Singleton{
private:

    static Singleton* s_instance;

    Singleton(){
        cout<<"Constructor called."<<endl;
    }

    ~Singleton(){
        cout<<"Destructor called."<<endl;
    }


public:

    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }
};

Singleton* Singleton::s_instance = nullptr;

void createInstance() {
    Singleton& instance = Singleton::getInstance();
}

int main() {
    std::thread t1(createInstance);
    std::thread t2(createInstance);

    t1.join();
    t2.join();

    return 0;
}
Output
Constructor called.
Destructor called.
Explaination

In earlier Singleton implementations, the instance is often stored and returned as a pointer. The instance is dynamically allocated with ‘new’, which requires manual memory management.
In modern C++ (C++11 and later), it’s recommended to use a reference returned by a function. This method is thread safe due to guaranteed static local variable initialisation in C++11. The instance is automatically created when first accessed and destroyed automatically when the program ends. Which is why “Destructor called” is also printed this time.

Conclusion

In this article, we showed how to make a Singleton safe to use with multiple threads by using a mutex and  Meyers’ Singleton. The mutex method works, but can slow things down because it locks every time you ask for the instance. Meyers’ Singleton is easier and faster because it uses C++ features that handle thread safety for us automatically. So, if you do not have a specific reason to manage memory manually, use the Mayers singleton method.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top