Data race on double checked locking

Double checked locking is a well known pattern used to initialize singleton. It works fine in some context, for instance it is absolutely fine using it to implement the Singleton Pattern with Java.

But when the implementation programming language is C++, the double checked locking strategy is often referred to as an anti-pattern - see for instance this wiki page on google code about data races.

The issue arise from the different meaning of the keyword volatile in Java vs. C++. In C++ volatile is just an hint to the compiler, meaning that we don't want any optimization performed on that variable, while in Java it implies constraints that ensures that any threads will read the most recent value of that variable.

So, that code that we see working fine for Java, is not equivalent with this (bad) rewrite in C++:
class SomethingExpensive
{
    // ...
};

volatile SomethingExpensive* se = nullptr; // !!! BAD IDEA !!!
boost::mutex mxse;

void setSomething()
{
    if(se == nullptr) // !!! BAD IDEA !!!
    {
        boost::lock_guard<boost::mutex> l(mxse);
        if(se == nullptr)
            se = new SomethingExpensive();
    }
}
The worst part of this code is that often, or even almost ever, it works. Just sometimes it gives an unexpected behavior that would be very difficult to track down to its real root.

4 comments:

  1. You need atomics: http://www.justsoftwaresolutions.co.uk/threading/multithreading-in-c++0x-part-6-double-checked-locking.html

    ReplyDelete
    Replies
    1. Thank you, Joshua. The link you passed it's very interesting and add a lot of substance to the post.

      Delete
  2. Just For your Information.
    We can implement Singleton without double check locking like below ...
    (From - Book - Modern C++ Design By - Andrei Alexandrescu )

    class Singleton
    {
    public:
    static Singleton* getInstance()
    {
    if ( !m_pSingleton )
    {
    Lock lock(mutex);
    if ( !m_pSingleton )
    m_pSingleton = new Singleton(47);
    }
    return m_pSingleton;
    }
    int getValue() { return i; }
    void setValue(int x) { i = x; }
    private:
    static Singleton* m_pSingleton;
    int i;
    Singleton(int x) : i(x) { }
    Singleton(const Singleton&); // Disallowed
    Singleton& operator=(Singleton&); // Disallowed
    ~Singleton(); // Disallowed
    };

    Singleton3 *Singleton::m_pSingleton = NULL;

    ReplyDelete
  3. Yes, sure. And thanks for quoting Alexandrescu, a fundamental reading. The "bad idea" I was pointing in the post was about using a technique designed to work in Java when developing in C++.

    You are right, I should have completed the discussion showing a viable alternative, as the one you quoted.

    ReplyDelete