1

I was reading this-link that talks about making a singleton implementation thread safe.

Excerpt from the link:

Making the classic Singleton implementation thread safe is easy. Just acquire a lock before testing pInstance:

Singleton* Singleton::instance() {
Lock lock; // acquire lock (params omitted for simplicity)
if (pInstance == 0) {
pInstance = new Singleton;
}

return pInstance;
} // release lock (via Lock destructor)

As few things were omitted for simplicity in the above program, I thought I should write a sample code similar to what was suggested above.

This is what I wrote:

class Singleton {
public:
    static Singleton* instance();
    ~Singleton();
    void disp() { cout << "disp() called"<< endl; }
private:
    static std::mutex mu2;
    static Singleton* pInstance;
};
std::mutex Singleton::mu2;
Singleton* Singleton::pInstance = NULL;

Singleton* Singleton::instance() {
    mu2.lock();
    if (pInstance == 0) {
        pInstance = new Singleton;
    }
    return pInstance;
}

Singleton::~Singleton() {
    cout << "destructor called." << endl;
    mu2.unlock();
}

void f1() {
    Singleton *p = Singleton::instance();
    p->disp();
}
int main()
{
    std::thread t1(f1);
    t1.join();
    getchar();
    return 0;
}

Output:

disp() called

f:\dd\vctools\crt\crtw32\stdcpp\thr\mutex.c(51): mutex destroyed while busy

My question is:

  1. What would be the implementation code of singleton class as per link's writer? without omitting anything?
  2. Why my sample program is getting runtime error.
  3. Is it possible to unlock the mutex in destructor?

I am running this code on Visual Studio.

Thank you.

Jatin
  • 1,857
  • 4
  • 24
  • 37

1 Answers1

3

To unlock a mutex, don't lock it by hand use std::unique_lock. Then it will be automatically unlocked after you leave a scope.

The way you do singleton with a pointer in global scope is asking for trouble at shutdown. Who's going to clean the singleton instance up? There isn't a delete anywhere.

Singleton* Singleton::instance() 
{
    std::unique_lock<std::mutex> lock{ mu2 }; // <=====
    if (pInstance == 0) {
        pInstance = new Singleton;
    }
    return pInstance;
}

// no unlock here, you only need to protect the creation.
Singleton::~Singleton() 
{
    cout << "destructor called." << endl;
}

void f1() 
{
    Singleton* p = Singleton::instance();
    p->disp();
}

The pattern you are looking for makes use of C++11 static initialization being threadsafe. And is described here : https://www.modernescpp.com/index.php/thread-safe-initialization-of-a-singleton

static auto& get_instance()
{
    static YourClass instance;
    return instance;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • 1
    @NathanOliver Thanks fixed (important detail that!) – Pepijn Kramer Sep 16 '21 at 12:21
  • Thanks P Kramer for pointing out that I should be using unique_lock. Can you answer my 2nd and 3rd question? Why my solution is crashing? and Is it possible to lock a mutex in constructor and unlock it in a destructor? – Jatin Sep 16 '21 at 12:30
  • The crash I would have to debug which on my phone I can't do. Locks should be as short as possible. In your case first get would lock. All other threads will not het anything and block at the lock. And destructor of your singleton will never be called because you use new and never (can) delete – Pepijn Kramer Sep 16 '21 at 12:45
  • @Jatin multiple locking without unlocking can lead to crashes. Locking in constructor and unclokcing the mutex in destructor is exactly what a `unique_lock` does – 463035818_is_not_an_ai Sep 16 '21 at 12:45