Is Singleton thread safe?

Is Singleton thread safe?

No, We need to make it thread safe by putting critical section under LOCK. Here critical section is the place where object is created. Otherwise there would be a case that every thread has its own object.

                                                 Without Locking mechanism

#include<pthread.h>
using  namespace std;
class Singleton
{
        static Singleton* object;
        int threadNo;
        Singleton(int threadId)
        {
                cout<<"\n threadId inside Singleton() = "<<threadId <<endl;
                threadNo=threadId;
        }
public:
        static Singleton*  getInstance( int threadId)
        {
                if(object == NULL)
                {
                                cout<<"\n Singleton Object is created first time";
                                cout<<"\n threadId inside getInstance = "<<threadId <<endl;
                                object= new Singleton(threadId);
                                return object;
                }
                else
                {
                        cout<<"\n Singleton Object is already created";
                        return object;
                }
        }
};
Singleton* Singleton:: object=NULL;
void* threadFuc(void * value)
{
        int* threadId=(int*)value;
        int threadIdValue=(int)*threadId;
        int index=0;
        while(1)
        {
                cout<<"\n\n\n threadIdValue = " <<*threadId <<endl;
                Singleton* object=Singleton::getInstance(threadIdValue);
                cout<<"\nThread Id="<< *threadId<<"  Object Value ="<< object <<endl;
                sleep(1)        ;
        }
}
int main()
{
        pthread_t thread[3];
        int value[3]={1,2,3};
        for(int i=0;i<3;i++) //Creating Threads
        {
                if(pthread_create(&thread[i],NULL,threadFuc,&value[i]))
                {
                        cout<<"\nError in thread creation\n";
                }
                else
                {
                        cout<<"\nThread is created\n";
                }
        }
        for(int i=0;i<3;i++)
        {
                if(pthread_join(thread[i],NULL))
                {
                        cout<<"\nUnable to join threads\n";
                }
                {
                        cout<<"\nThread is joined\n";
                }
        }
        return 0;
}
                                                               

Output::

Thread is created

threadIdValue =1
Thread is created
Thread is created

Singleton Object is created first time
threadId inside getInstance = 1

threadIdValue = 2
threadId inside Singleton() =

threadIdValue = 3
Singleton Object is created first time
threadId inside getInstance = 3


Singleton Object is created first time
threadId inside getInstance = 2

threadId inside Singleton() = 2


Thread Id=2  Object Value =0x191c13c0

threadId inside Singleton() = 3

Thread Id=3  Object Value =0x191c13e0


Thread Id=1  Object Value =0x191c13a0



From above example it is clear that every thread has created its own singleton object that beats our purpose to use singleton. Now put critical section under LOCK and check difference in output



                                        Case -1  With lock applied to complete If

#include<iostream>
#include<pthread.h>
#include<stdio.h>
using  namespace std;

pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
class Singleton
{
         static Singleton* object;
        int threadNo;
        Singleton(int threadId)
        {
                cout<<"\n threadId inside Singleton() = "<<threadId<<endl;fflush(stdout);
                threadNo=threadId;
        }
public:
        static Singleton*  getInstance( int threadId)
        {

                cout<<"\n**** waiting on pthread_mutex_lock *** ::  "<<threadId <<endl;fflush(stdout);
                pthread_mutex_lock(&mut); //Lock taken by thread
                if(object == NULL)
                {
                    cout<<"\n***** Lock acquired by (FIRST) pthread_mutex_lock *** ::  "<<threadId<<endl;fflush(stdout);
                    cout<<"\n Singleton Object is created first time :: threadId inside getInstance = "<<threadId ;
                                object= new Singleton(threadId);
                                cout<<"Object Value ="<< object <<endl;fflush(stdout);
                                pthread_mutex_unlock(&mut);  //Released by thread
                                return object;
                }
                else
                {
                        cout<<"\n Singleton Object is already created :: threadId  = "<<threadId <<endl;fflush(stdout);
                        pthread_mutex_unlock(&mut); //Released by thread
                        return object;
                }
        }
        void placeOrder()

};

Singleton* Singleton:: object=NULL;


void* threadFuc(void * value)
{

        int* threadId=(int*)value;
        int threadIdValue=(int)*threadId;
        int index=0;
        while(1)
        {
                cout<<"\n\n\n threadIdValue = " <<*threadId <<endl;fflush(stdout);
                Singleton* object=Singleton::getInstance(threadIdValue);
                cout<<"\nThread Id="<< *threadId<<"  Object Value ="<< object <<endl;fflush(stdout);
                sleep(1)        ;
       }
}

int main()
{
        pthread_t thread[3];
        int value[3]={1,2,3};
        for(int i=0;i<3;i++)
        {
                if(pthread_create(&thread[i],NULL,threadFuc,&value[i]))
                {
                        cout<<"\nError in thread creation\n";
                }
                else
                {
                        cout<<"\nThread is created ID = "<<(value[i])<<endl; fflush(stdout);
                }
        }

        for(int i=0;i<3;i++)
        {
                if(pthread_join(thread[i],NULL))
                {
                        cout<<"\nUnable to join threads\n";
                }
                {
                        cout<<"\nThread is joined\n";
                }
        }
        return 0;
}


Output::

Thread is created ID =1
threadIdValue = 1


**** waiting on pthread_mutex_lock *** ::  1

***** Lock acquired by (FIRST) pthread_mutex_lock *** ::  1

Singleton Object is created first time :: threadId inside getInstance = 1

Thread is created ID = 2
threadIdValue = 2

Thread is created ID = 3
threadIdValue = 3

**** waiting on pthread_mutex_lock *** ::  3

threadId inside Singleton() = 1
Object Value =0xf88a270

**** waiting on pthread_mutex_lock *** ::
Thread Id=1  Object Value =0xf88a270

Singleton Object is already created :: threadId  = 3

Thread Id=3  Object Value =0xf88a270

Singleton Object is already created :: threadId  = 0x2

Thread Id=0x2  Object Value =0xf88a270




threadIdValue = 1

**** waiting on pthread_mutex_lock *** ::  1

Singleton Object is already created :: threadId  = 1

Thread Id=1  Object Value =0xf88a270



threadIdValue = 2

**** waiting on pthread_mutex_lock *** ::  2


threadIdValue = 3

**** waiting on pthread_mutex_lock *** ::  3



Just check for highlighted statements in output they are self explanatory to how an object is created singleton thread Single check scenario.

Here only one object is created as print "***** Lock acquired by (FIRST) pthread_mutex_lock *** ::  1" is only once, but issue is that very time a thread need to use a singleton object then it has do a heavy operation of locking and unlocking of mutex in every single iteration. This mechanism has solved the problem explained in step-1 i.e. every thread has its own Singleton Object but requires heavy lock and unlock operation in every iteration. To overcome this issue one should change locking mechanism let see case -2

Lock Case -2
To limit locking issue explained in Case-1, it would be better to put Lock inside NULL check section so that once object is created locking is not required.




                        Case -2  With lock applied inside IF

class Singleton
{

        static const Singleton*  object;
        int threadNo;
        Singleton(int threadId)
        {
                cout<<"\n threadId inside Singleton() = "<<threadId<<endl;fflush(stdout);
                threadNo=threadId;
        }
public:

        static const Singleton*  getInstance( int threadId)
        {

                if(object == NULL)
                {
                    cout<<"\n**** waiting on pthread_mutex_lock *** ::  "<<threadId <<endl;
                    pthread_mutex_lock(&mut);
                    cout<<"\n***** Lock acquired by (FIRST) pthread_mutex_lock *** :: "<<threadId<<endl;
                    cout<<"\n Singleton Object is created first time :: threadId inside getInstance = "<<threadId <<endl;
                    object= new Singleton(threadId);
                    cout<<"Object Value ="<< object <<endl;fflush(stdout);
                    pthread_mutex_unlock(&mut);
                    return object;
                }
                else
                {
                  cout<<"\n Singleton Object is already created :: threadId  = "<<threadId <<endl;
                  return object;
                }
        }

};



Output:-

[localhost singleton]$ ./a.out

Thread is created ID =1

 threadIdValue = 1

**** waiting on pthread_mutex_lock *** ::  1

Thread is created ID =2
***** Lock acquired by (FIRST) pthread_mutex_lock *** ::  1

 threadIdValue =2
 Singleton Object is created first time :: threadId inside getInstance = 1


**** waiting on pthread_mutex_lock *** ::  2


Thread is created ID =3
 threadId inside Singleton() = 1

 threadIdValue = Object Value =3

 Singleton Object is already created :: threadId  = 0x3

Thread Id=0x3  Object Value =0x80d43a0
0x80d43a0

Thread Id=1  Object Value =0x80d43a0

***** Lock acquired by (FIRST) pthread_mutex_lock *** ::  2

 Singleton Object is created first time :: threadId inside getInstance = 2

 threadId inside Singleton() = 2
Object Value =0x80d43c0

Thread Id=2  Object Value =0x80d43c0
                                                     
Thread Id=2  Object Value =0x80d43c0



 threadIdValue = 1

 Singleton Object is already created :: threadId  = 1

Thread Id=1  Object Value =0x80d43c0



 threadIdValue = 3

 Singleton Object is already created :: threadId  = 3

Thread Id=3  Object Value =0x80d43c0


In above example every thread is using the same Object and locking mechanism is not used once object is already created. But Issue is still with creation "***** Lock acquired by (FIRST) pthread_mutex_lock *** ::" as statement is came twice, context switched as soon as one thread gets the lock as the same time thread 2 checks that object is still NULL and comes inside null check and waits on LOCK. Moreover Both threads creates objects as object values "Thread Id=2  Object Value =0x80d43c0" and "Thread Id=1 Object Value =0x80d43a0" both are different. while object created by later thread is used leads to memory leak therefore need to alter locking mechanism one more time.

It would be best that a thread shall check that singleton object variable is NULL or not even after acquiring lock lets see how it works.


                       Case -2  Double Check Singleton Mechanism

class Singleton
{

        static const Singleton*  object;
        int threadNo;
        Singleton(int threadId)
        {
                cout<<"\n threadId inside Singleton() = "<<threadId<<endl;fflush(stdout);
                threadNo=threadId;
        }
public:

        static const Singleton*  getInstance( int threadId)
        {

                if(object == NULL)
                {
                   pthread_mutex_lock(&mut);

                   if(object == NULL)
                   {
                     cout<<"\n**** waiting on pthread_mutex_lock *** ::  "<<threadId <<endl;
                     cout<<"\n***** Lock acquired by (FIRST) pthread_mutex_lock *** ::  "<<threadId<<endl;
                     cout<<"\n Singleton Object is created first time :: threadId inside getInstance = "<<threadId <<endl;
                     object= new Singleton(threadId);
                     cout<<"Object Value ="<< object <<endl;fflush(stdout);
                     pthread_mutex_unlock(&mut);
                     return object;
                   }
                   pthread_mutex_unlock(&mut);
                   return object;
                }
                else
                {
                   cout<<"\n Singleton Object is already created :: threadId  = "<<threadId <<endl;
                   return object;
                }
        }

};


Output


[localhost singleton]$ ./a.out

Thread is created ID = 1

**** waiting on pthread_mutex_lock *** ::  1

***** Lock acquired by (FIRST) pthread_mutex_lock *** ::  1

Singleton Object is created first time :: threadId inside getInstance = 1

threadId inside Singleton() =1

threadIdValue = 2

Thread is created ID = 2

Object Value =0x13f2f270

Thread Id=1  Object Value =0x13f2f270

Thread Id=2
Thread is created ID = 3  Object Value =0x13f2f270


 threadIdValue = 0x3
 Singleton Object is already created :: threadId  = 0x3
Thread Id=0x3  Object Value =0x13f2f270



 threadIdValue = 1
 Singleton Object is already created :: threadId  = 1
Thread Id=1  Object Value =0x13f2f270



 threadIdValue = 2
 Singleton Object is already created :: threadId  = 2
Thread Id=2  Object Value =0x13f2f270

It is very Clear from above example that "***** Lock acquired by (FIRST) pthread_mutex_lock ***" is printed once, implies only one thread has created an object and same object is used by all other threads. This is the best way to make singleton as thread-safe. It generally called as double check Singleton because we check Obeject==NULL twice one before getting lock and other is after getting lock.


Your Comments /Suggestions and Questions are always welcome,  shall clarify with best of knowledge. So feel free to put Questions. 

No comments:

Post a Comment