C++11: Safe double checked locking for lazy initialization. Possible?
Asked Answered
W

2

13

I have read many questions considering thread-safe double checked locking (for singletons or lazy init). In some threads, the answer is that the pattern is entirely broken, others suggest a solution.

So my question is: Is there a way to write a fully thread-safe double checked locking pattern in C++? If so, how does it look like.

We can assume C++11, if that makes things easier. As far as I know, C++11 improved the memory model which could yield the needed improvements.

I do know that it is possible in Java by making the double-check guarded variable volatile. Since C++11 borrowed large parts of the memory model from the one of Java, so I think it could be possible, but how?

Whitish answered 6/9, 2012 at 14:10 Comment(9)
If you can use C++11 just ignore the whole double checked locking business and use either static local variables or std::call_once.Fidele
Are static locals initialized lazily? And about call_once: How does this ensure that call once will not write the not fully created reference to the variable?Whitish
yes, static locals are initialized lazily in a thread-safe manner. And call_once ensures the subject is only ever called once; and that no other call to call_once returns before the one that actually executes the function returns (you can read more here en.cppreference.com/w/cpp/thread/call_once). How it does that is up to the implementation. These two things basically exist so you don't want to bother with writing more <strike>bugs</strike> double-checked locking implementations.Fidele
Static local variables must surely be one of the most elegant solutions.Erskine
still, call_once might not yield the performance benefits of the double-checked-locking pattern. It only ensures one time execution. However, the static local var thingy could be the solution.Whitish
Note that, as Yogi Berra said: In theory, theory and practice are the same. In practice they aren't. There are some issues implementing the C++11 memory model in practice in some platforms.Handler
Is the thread-safe semantics of static local variables a C++11 only feature? Because I found a blog post that argues that it is indeed NOT thread safe and is even required to be that way by the specification: blogs.msdn.com/b/oldnewthing/archive/2004/03/08/85901.aspxWhitish
@Whitish Yes, it's only in C++11.Fidele
great, thanks! Seems that C++ is going into the right direction with the new standard. Very elegant solution!Whitish
C
18

Simply use a static local variable for lazily initialized Singletons, like so:

MySingleton* GetInstance() {
  static MySingleton instance;
  return &instance; 
}

The (C++11) standard already guarantees that static variables are initialized in a threadsafe manner and it seems likely that the implementation of this at least as robust and performant as anything you'd write yourself.

The threadsafety of the initialization can be found in §6.7.4 of the (C++11) standard:

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.

Caveman answered 6/9, 2012 at 14:28 Comment(5)
This is insane. Why, oh why, would you ever return a pointer if it can never be null ?Brockie
@MatthieuM.: Mainly because it makes people less inclined to copy the underlying object. Of course a well designed singleton shouldn't have a copy constructor, but still. I really don't see how return by reference vs. return by value matters in that case. Therefore I would hardly call that insane.Caveman
@Grizzly: If an object should not be copyable, it is up to the object to enforce that. If an object should have an instance that is globally accessible, there should be a function to handle that (like yours). These two things are separate, and there's no reason to combine them. This is why the Singleton pattern is stupid.Christiansand
Insane because some pople might delete MySingleton::GetInstance, or might if ( MySingleton::GetInstance() ) {}. It makes more sense to return a reference and set copy ctor/assignment to deleted. Or if it has to be a pointer make the dtor private.Alvord
Doesn't answer question (although it certainly provides a good answer to a different question: "How to efficiently initialize a global singleton in a thread-safe manner").Counterpart
D
3

Since you wanted to see a valid DCLP C++11 implementation, here is one.

The behavior is fully thread-safe and identical to GetInstance() in Grizzly's answer.

std::mutex mtx;
std::atomic<MySingleton *> instance_p{nullptr};

MySingleton* GetInstance()
{
    auto *p = instance_p.load(std::memory_order_acquire);

    if (!p)
    {
        std::lock_guard<std::mutex> lck{mtx};

        p = instance_p.load(std::memory_order_relaxed);
        if (!p)
        {
            p = new MySingleton;
            instance_p.store(p, std::memory_order_release);
        }
    }

    return p;
}
Divaricate answered 19/8, 2017 at 4:9 Comment(3)
In practice, you'd want to declare your mtx and instance_p variables as globals outside of the function, rather than as statics within the function, since otherwise you are paying the price for the compiler's internal checks about the initialization of mtx and instance_p on every call, defeating the point of double-checked locking (since performance-wise you might as well just declare the singleton as a static then).Counterpart
You might want to add that you basically never want to write this code though. Grizzly's answer is more concise plus the compiler might insert more magic in the future to make it faster than this code.Whitish
@Whitish Of course this is low level stuff that is normally reserved for libraries,etc.. but you literally asked for an implementation.. "Is there a way to write a fully thread-safe double checked locking pattern in C++? If so, how does it look like"Divaricate

© 2022 - 2024 — McMap. All rights reserved.