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.
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>
#include<iostream>
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.
PREV::Singleton Design Pattern
PREV::Singleton Design Pattern
Factory Design Pattern::NEXT
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