Click here to Skip to main content
15,867,704 members
Articles / Programming Languages / C++

Double-Checked Locking Optimization

Rate me:
Please Sign up or sign in to vote.
4.57/5 (9 votes)
26 Oct 2009CPOL5 min read 81.2K   28   30
The Double-Checked Locking Optimization Design Pattern reduces contention and synchronization overheads whenever critical sections of code must acquire locks in a thread-safe manner just once during program execution.

Introduction

This article is related to my previous article: The Strategized Locking Design Pattern. The Double-Checked Locking Optimization Design Pattern reduces contention and synchronization overhead whenever critical sections of code must acquire locks in a thread-safe manner just once during program execution. The Singleton pattern is an excellent example to explain this pattern. However, this pattern is not tied to the Singleton pattern.

Detailed Information

Detailed information can be found in this book: Pattern-Oriented Software Architecture (Volume 2).

There are three major articles related to this pattern. These articles should be read very carefully, and should be considered as a must to read before start implementing this pattern in a multithreaded environment. These references explain the weaknesses of the constructions used within this pattern in further detail.

Detailed Information

The Double-Checked Locking Optimization Pattern (DCLP) is clarified in a multithreaded environment in combination with the Singleton pattern (GoF) and the volatile keyword. I will start with the problem. The problem is very simple. However, the solution with the DCLP which is proposed by Douglas Schmidt is still not safe in a multithreaded environment. Despite the weak spots, the DCLP idea is very interesting.

The DCLP created conversations on the top shelf in the C++ community. Scott Meyers took the pattern of Douglas Schmidt and clarified the weak spots. He not only points out the problems but also explains why. I will try to explain the ideas of Mr. Meyers in this article.

My intention is not to exceed any of these great people, including Mr. Schmidt. My intention is to summarize the DCLP advantages and disadvantages. If you like to read more about the basic Singleton pattern, please stop reading this article. This article is about the Double-Checked Locking Optimization Pattern.

Problem

Here is how we get the instance of the Singleton object:

The header .h file:

C++
static SingletonObject& GetInstance( );

The implementation .cpp file:

C++
SingletonObject& SingletonObject::GetInstance( )
{  
    if( m_instance == NULL )
    {
        m_instance = new SingletonObject( );
    }    
    return *m_instance;
}

The Singleton's constructor shown above can be called multiple times in a multi-threaded application. It will lead to memory leaks. This could be disastrous since you have created multiple instances of the object.

Challenge 1

You can solve this with the help of the Strategized Locking Pattern described here: The Strategized Locking Design Pattern.

The problem is solved by using the Guard before the conditional check. The Singleton is now thread safe. However, you may have created a locking overhead.

The header .cpp file:

C++
static SingletonObject& GetInstance( );

private:
     Lock m_lock;

The implementation .cpp file:

C++
SingletonObject& SingletonObject::GetInstance( )
{  
    Guard< Lock > lock( m_lock );

    if( m_instance == NULL )
    {
        m_instance = new SingletonObject( );
    }    
    return *m_instance;
}

Challenge 2

The locking overhead can be solved by placing the Guard inside the conditional check. The implementation .cpp file:

C++
SingletonObject& SingletonObject::GetInstance( )
{
    if( m_instance == NULL )
    {
        Guard< Lock > lock( m_lock );
         m_instance = new SingletonObject( );
    }    
    return *m_instance;
}

Unfortunately, this solution does not provide thread safe initialization because a race condition in the multi-threaded application can cause multiple initializations of the Singleton. Consider two threads that simultaneously check for instance == NULL. Both will succeed; one will acquire the lock via the Guard, and the other will block. After the first thread initializes the Singleton and releases the lock, the blocked thread will obtain the lock and erroneously initialize the Singleton a second time.

Challenge 3 (Double-Checked Locking Optimization)

The DCLP starts at this point of the problem. As already mentioned before, the DCLP proposed by Douglas Schmidt is still not thread safe. The weakness of this pattern is it uses volatile in order to realize multi-threading safety. Hans Boehm and Nick Maclaren explain in their article when to use and when not to use the volatile keyword. In the case of DCLP, it should not be used. Therefore, this part will wipe away the "thread-safe manner during program execution" of the DCLP. This means the description of DCLP is no longer valid and can be considered as not safe.

To realize the DCLP, the conditional check should be placed after the Guard, and the pointer to the instance should be declared as volatile.

The header .h file:

C++
Class SingletonObject
{
public:
    static SingletonObject* GetInstance( );
    …
private:
    static SingletonObject* volatile m_instance;
}

The implementation .cpp file:

C++
SingletonObject& SingletonObject::GetInstance( )
{
    if( m_instance == NULL )
    {
        Guard< Lock > lock( m_lock );

        if( m_instance == NULL )
        {
             m_instance = new SingletonObject( );
        }
    }    
    return *m_instance;
}

What is volatile?

"The volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided."

-Bjarne Stroustrup, 3rd edition

Try to Avoid the DCLP

There is a small and tiny drawback using this pattern. However, this tiny drawback can have disastrous consequences for your application. The weakness of this pattern is when the Singleton is just being created in the virtual memory and simultaneously multiple threads are calling the GetInstance method of the Singleton.

Detailed information can be found in the article by Scott Meyers (the C++ master): Scott Meyers and Andrei Alexandrescu.

Example:

The Singleton object is not created yet and the instance is NULL.

Thread 1: is creating the Singleton object (the Singleton is not created in the virtual memory yet) due to optimizing actions taken by the compiler.

C++
m_instance = new SingletonObject( )

However, the pointer to the Singleton is already created and is not NULL.

Thread 2: this thread gets focus, and will not fall through the first conditional check since the pointer is valid. As already mentioned before, the Singleton object is not created in the memory yet and the instance is returned.

Thread 2: will crash using this pointer, since it’s pointing to a memory which is not allocated yet.

Extended DCLP

At this point, Scott Meyers jumps in. He explains that DCLP must be extended with one more volatile. However, this is only valid if the volatile is thread safe. This means even though the extra volatile is added, the problem is still not solved. On the contrary, it can introduce performance issues.

The header .h file:

C++
Class SingletonObject
{
public:
    static SingletonObject* GetInstance( );
    …
private:
    static SingletonObject* volatile m_instance;
}

The implementation .cpp file:

C++
SingletonObject* SingletonObject::GetInstance( )
{
    if( m_instance == NULL )
    {
        Guarder< Lock > lock( m_lock );
       
        if( m_instance == NULL )
        {
            SingletonObject* volatile temp = 
              static_cast< SingletonObject* >(operator new (sizeof(SingletonObject)));
            m_instance = temp;
        }
    }
    return m_instance;
}

In this part, the instance of the Singleton is created in a different way to reduce the aggressive optimization of the compiler.

C++
SingletonObject* volatile temp = 
  static_cast< sSingletonObject* >(operator new (sizeof(SingletonObject)));
m_instance = temp;

Conclusion

The Double-Checked Locking Pattern is not thread safe in combination with the Singleton pattern. Moreover, extending the DCLP with one more volatile will cause more harm than good. In other cases, the implementation of the DCLP should be analyzed very carefully. It is better to avoid it than playing the hero. In the case of the Singleton pattern, make sure the Singleton object is created before it can be accessed by multiple threads. According to Scott Meyers' article, it can only be fixed with memory barriers.

By the way, Scott Meyers is the man! :)

Thread Safe Singleton Pattern

Codeplug demonstrates this memory barrier. He creates a thread safe Singleton pattern with help of the DCLP.

Special Thanks To

  • Codeplug (Member 4490530).
  • Riced (David R).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Netherlands Netherlands
I'm a C++ and C# .Net software engineer.

Comments and Discussions

 
Answervolatile has nothing to do with multi-threading, at least in the C++03 Pin
Codeplug-gg21-Oct-09 8:25
Codeplug-gg21-Oct-09 8:25 
GeneralRe: volatile has nothing to do with multi-threading, at least in the C++03 Pin
Hamed Ebrahimmalek22-Oct-09 2:32
Hamed Ebrahimmalek22-Oct-09 2:32 
GeneralRe: volatile has nothing to do with multi-threading, at least in the C++03 Pin
Codeplug-gg22-Oct-09 4:32
Codeplug-gg22-Oct-09 4:32 
GeneralRe: volatile has nothing to do with multi-threading, at least in the C++03 Pin
Hamed Ebrahimmalek22-Oct-09 20:42
Hamed Ebrahimmalek22-Oct-09 20:42 
GeneralRe: volatile has nothing to do with multi-threading, at least in the C++03 Pin
Codeplug-gg23-Oct-09 4:27
Codeplug-gg23-Oct-09 4:27 
GeneralRe: volatile has nothing to do with multi-threading, at least in the C++03 Pin
Hamed Ebrahimmalek23-Oct-09 4:30
Hamed Ebrahimmalek23-Oct-09 4:30 
GeneralRe: volatile has nothing to do with multi-threading, at least in the C++03 Pin
Hamed Ebrahimmalek23-Oct-09 4:30
Hamed Ebrahimmalek23-Oct-09 4:30 
Hi gg,

Let's take it to another level...
I really like your article, it's very interesting. However, your Singleton pattern is NOT thread safe. I can break it very easily. I bring out some issues first before I go into the "not safe" part.

Let us first see what you have done in your article.

You are using the DCL pattern (Double-Checked locking Optimization) of D. Schmidt. You modified it with thanks to Scott Meyers and Andrei Alexandrescu a link to those articles can be very interesting.

According to Andrew Koeing "Ruminations on C++" and Herb Sutter C++ "Coding Standards" the destructor should be made virtual. So, you should make your no_copy destructor virtual.

You explain the creation of the Singleton and not the destruction. Either you accept the memory leak or the weak construction.

You have locked the Instance method where the DCLP technique is used. However, the unload method is not guarded. You can now unload/delete the one and only instance while creating the SingletonObject. This means the code your wrote is incorrect and NOT thread safe!

T *p = InterlockedReadAcquire(&m_p);
if (!p)
{
CriticalSection::scoped_lock lock(m_cs);
p = m_p;

Please set the pointer back to NULL/0 when you delete it, so the statement above can be executed correctly without using a pointer which point to a deleted Singleton object. This means, if I put the creating and the destructing in different threads, your construction will fail and is not thread safe. So, what can we conclude? We can conclude that your example is still not thread safe.

The Display methods can be declared as const method.

Lets talk about the friend class you're using. It's like slapping the Object Oriented concept with both hands. I can still Unload the one and only instance. So, you shouldn't declare the entire class as friend but only the methods using.

P.S. for the next time you post a message and criticize people on details make sure your own creation is good enough.

Hamed

GeneralRe: volatile has nothing to do with multi-threading, at least in the C++03 Pin
Codeplug-gg23-Oct-09 6:05
Codeplug-gg23-Oct-09 6:05 
QuestionWhy wouldn't this work... Pin
rmf5212-May-09 7:27
rmf5212-May-09 7:27 
AnswerRe: Why wouldn't this work... Pin
Hamed Ebrahimmalek20-May-09 1:54
Hamed Ebrahimmalek20-May-09 1:54 
AnswerRe: Why wouldn't this work... Pin
Ricky Lung26-Oct-09 16:46
Ricky Lung26-Oct-09 16:46 
GeneralRe: Why wouldn't this work... Pin
supercat927-Oct-09 6:08
supercat927-Oct-09 6:08 
GeneralNot the best option or even a good option. Pin
fresi16-Feb-09 21:19
fresi16-Feb-09 21:19 
GeneralRe: Not the best option or even a good option. Pin
Hamed Ebrahimmalek16-Feb-09 22:16
Hamed Ebrahimmalek16-Feb-09 22:16 
GeneralRe: Not the best option or even a good option. Pin
Donsw4-Mar-09 10:17
Donsw4-Mar-09 10:17 
GeneralRe: Not the best option or even a good option. Pin
Hamed Ebrahimmalek5-Mar-09 4:45
Hamed Ebrahimmalek5-Mar-09 4:45 
GeneralDCL question Pin
FirebugMaker16-Feb-09 11:29
FirebugMaker16-Feb-09 11:29 
GeneralRe: DCL question Pin
Axel Rietschin16-Feb-09 13:19
professionalAxel Rietschin16-Feb-09 13:19 
GeneralRe: DCL question Pin
FirebugMaker17-Feb-09 3:42
FirebugMaker17-Feb-09 3:42 
GeneralDouble checked locking considered harmful Pin
riced11-Feb-09 5:59
riced11-Feb-09 5:59 
GeneralRe: Double checked locking considered harmful [modified] Pin
Hamed Ebrahimmalek11-Feb-09 7:30
Hamed Ebrahimmalek11-Feb-09 7:30 
GeneralRe: Double checked locking considered harmful Pin
supercat913-Feb-09 17:30
supercat913-Feb-09 17:30 
GeneralRe: Double checked locking considered harmful Pin
riced14-Feb-09 6:36
riced14-Feb-09 6:36 
GeneralRe: Double checked locking considered harmful Pin
Hamed Ebrahimmalek16-Feb-09 1:07
Hamed Ebrahimmalek16-Feb-09 1:07 
GeneralRe: Double checked locking considered harmful Pin
riced16-Feb-09 2:04
riced16-Feb-09 2:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.