Asked  7 Months ago    Answers:  5   Viewed   32 times

Is the following implementation, using lazy initialization, of Singleton (Meyers' Singleton) thread safe?

static Singleton& instance()
{
     static Singleton s;
     return s;
}

If not, why and how to make it thread safe?

 Answers

37

In C++11, it is thread safe. According to the standard, ยง6.7 [stmt.dcl] p4:

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

GCC and VS support for the feature (Dynamic Initialization and Destruction with Concurrency, also known as Magic Statics on MSDN) is as follows:

  • Visual Studio: supported since Visual Studio 2015
  • GCC: supported since GCC 4.3

Thanks to @Mankarse and @olen_gam for their comments.


In C++03, this code wasn't thread safe. There is an article by Meyers called "C++ and the Perils of Double-Checked Locking" which discusses thread safe implementations of the pattern, and the conclusion is, more or less, that (in C++03) full locking around the instantiating method is basically the simplest way to ensure proper concurrency on all platforms, while most forms of double-checked locking pattern variants may suffer from race conditions on certain architectures, unless instructions are interleaved with strategically places memory barriers.

Tuesday, June 1, 2021
 
QuantumMechanic
answered 7 Months ago
96

This is a singleton because static storage duration for a function local means that only one instance of that local exists in the program.

Under the hood, this can very roughly be considered to be equivalent to the following C++98 (and might even be implemented vaguely like this by a compiler):

static bool __guard = false;
static char __storage[sizeof(Singleton)]; // also align it

Singleton& Instance() {
  if (!__guard ) {
    __guard = true;
    new (__storage) Singleton();
  }
  return *reinterpret_cast<Singleton*>(__storage);
}

// called automatically when the process exits
void __destruct() {
  if (__guard)
    reinterpret_cast<Singleton*>(__storage)->~Singleton();
}

The thread safety bits make it get a bit more complicated, but it's essentially the same thing.

Looking at an actual implementation for C++11, there is a guard variable for each static (like the boolean above), which is also used for barriers and threads. Look at Clang's AMD64 output for:

Singleton& instance() {
   static Singleton instance;
   return instance;
}

The AMD64 assembly for instance from Ubuntu's Clang 3.0 on AMD64 at -O1 (courtesy of http://gcc.godbolt.org/ is:

instance():                           # @instance()
  pushq %rbp
  movq  %rsp, %rbp
  movb  guard variable for instance()::instance(%rip), %al
  testb %al, %al
  jne   .LBB0_3
  movl  guard variable for instance()::instance, %edi
  callq __cxa_guard_acquire
  testl %eax, %eax
  je    .LBB0_3
  movl  instance()::instance, %edi
  callq Singleton::Singleton()
  movl  guard variable for instance()::instance, %edi
  callq __cxa_guard_release
.LBB0_3:
  movl  instance()::instance, %eax
  popq  %rbp
  ret

You can see that it references a global guard to see if initialization is required, uses __cxa_guard_acquire, tests the initialization again, and so on. Exactly in almost every way like version you posted from Wikipedia, except using AMD64 assembly and the symbols/layout specified in the Itanium ABI.

Note that if you run that test you should give Singleton a non-trivial constructor so it's not a POD, otherwise the optimizer will realize that there's no point to doing all that guard/locking work.

Saturday, June 5, 2021
 
DilbertDave
answered 6 Months ago
31

There are certain places where traditional functional techniques make a lot of sense, and lead to code that is both smaller and more concise. A classic example is text parsing and tree processing, both often appearing together when you're implementing a DSL. F# features like anonymous iterators, extensible pattern matching, and ability to define custom infix operators to serve as combinators really helps a lot here. Meanwhile, on the C# side, LINQ is a good start, but it doesn't take you all the way there.

I suggest you have a look at FParsec, and see for yourself how much better suited it is to advanced text processing / parsing than any library you could possibly write in C#.

Sunday, August 8, 2021
 
TheCarver
answered 4 Months ago
18

Is a read operation that iterates the queue and accesses the corresponding items in the list thread safe?

Is it documented as being thread safe?

If no, then it is foolish to treat it as thread safe, even if it is in this implementation by accident. Thread safety should be by design.

Sharing memory across threads is a bad idea in the first place; if you don't do it then you don't have to ask whether the operation is thread safe.

If you have to do it then use a collection designed for shared memory access.

If you can't do that then use a lock. Locks are cheap if uncontended.

If you have a performance problem because your locks are contended all the time then fix that problem by changing your threading architecture rather than trying to do dangerous and foolish things like low-lock code. No one writes low-lock code correctly except for a handful of experts. (I am not one of them; I don't write low-lock code either.)

Even if the list is resized when it is being indexed, I am only accessing an item that already exists, so it does not matter whether it is read from the old array or the new array.

That's the wrong way to think about it. The right way to think about it is:

If the list is resized then the list's internal data structures are being mutated. It is possible that the internal data structure is mutated into an inconsistent form halfway through the mutation, that will be made consistent by the time the mutation is finished. Therefore my reader can see this inconsistent state from another thread, which makes the behaviour of my entire program unpredictable. It could crash, it could go into an infinite loop, it could corrupt other data structures, I don't know, because I'm running code that assumes a consistent state in a world with inconsistent state.

Monday, October 11, 2021
 
Limon
answered 2 Months ago
32

If you want to have the data source to always be on the safe side of concurrency, you should have at least one pointer that is always safe for him to use. So the Observer object should have a lifetime that isn't ended before that of the data source.

This can be done by only adding Observers, but never removing them. You could have each observer not do the core implementation itself, but have it delegate this task to an ObserverImpl object. You lock access to this impl object. This is no big deal, it just means the GUI unsubscriber would be blocked for a little while in case the observer is busy using the ObserverImpl object. If GUI responsiveness would be an issue, you can use some kind of concurrent job-queue mechanism with an unsubscription job pushed onto it. ( like PostMessage in Windows )

When unsubscribing, you just substitute the core implementation for a dummy implementation. Again this operation should grab the lock. This would indeed introduce some waiting for the data source, but since it's just a [ lock - pointer swap - unlock ] you could say that this is fast enough for real-time applications.

If you want to avoid stacking Observer objects that just contain a dummy, you have to do some kind of bookkeeping, but this could boil down to something trivial like an object holding a pointer to the Observer object he needs from the list.

Optimization : If you also keep the implementations ( the real one + the dummy ) alive as long as the Observer itself, you can do this without an actual lock, and use something like InterlockedExchangePointer to swap the pointers. Worst case scenario : delegating call is going on while pointer is swapped --> no big deal all objects stay alive and delegating can continue. Next delegating call will be to new implementation object. ( Barring any new swaps of course )

Tuesday, October 26, 2021
 
LDropl
answered 1 Month ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share