Stream safe lazy building singleton code in C ++

Is there a way to implement a single object in C ++ that:

  • Lena is built in streaming safe mode (two streams can be the first user of a single-element device at the same time - it still needs to be built only once).
  • It does not rely on static variables that are created in advance (therefore, a singleton object in itself is safe to use when building static variables).

(I don’t know my C ++ very well, but in this case integral and constant static variables are initialized before any code is executed (that is, even before static constructors) can their values ​​be already “initialized” into the program image)? If so, it could possibly be used to implement a singleton mutex, which in turn can be used to protect the creation of a real singleton ..)




Great, it seems that I have some good answers (shame, I cannot mark 2 or 3 as the answer ). There seem to be two broad solutions:

  • Use static initialization (as opposed to dynamic initialization) of the POD static variable and the implementation of my own mutex using the built-in atomic instructions. This was the type of solution that I spoke about in my question, and, I think, I already knew.
  • Use some other library function, for example pthread_once or boost :: call_once . Of course, I did not know about this - and I am very grateful for the published answers.
+31
c ++ multithreading singleton lazy-initialization construction
Aug 09 '08 at 21:16
source share
10 answers

Basically, you request synchronized creation of singleton code without using any synchronization (previously constructed variables). In general, no, this is not possible. You need something available for synchronization.

As with your other question, yes, static variables that can be statically initialized (i.e., raw code) must be initialized before another code is executed. This allows you to use a statically initialized mutex to synchronize the creation of a singleton.

In the 2003 edition of the C ++ standard:

Objects with a static storage duration (3.7.1) must be initialized with zeros (8.5) before any other initialization. Zero initialization and constant expression initialization are collectively called static initialization; all other initialization is dynamic initialization. Objects of POD types (3.9) with static storage duration initialized with constant expressions (5.19) must be initialized before any dynamic initialization begins. Objects with a static storage duration defined in the namespace area in the same translation system and dynamically initialized must be initialized in the order in which their definition appears in the translation block.

If you know that you will use this singleton during the initialization of other static objects, I think you will find that synchronization is not a problem. As far as I know, all major compilers initialize static objects in a single thread, so thread safety with static initialization. You can declare your plain pointer to NULL, and then check to see if it is initialized before using it.

However, this assumes that you know that you will use this singleton during static initialization. It is also not guaranteed by the standard, so if you want to be completely safe, use a statically initialized mutex.

Edit: Chris's suggestion of using atomic change and replacement will certainly work. If portability is not a problem (and creating additional temporary singletons is not a problem), then this is a slightly lower utility solution.

+11
Aug 09 '08 at 23:52
source share

Unfortunately, Matt responds to functions called double-check locking, which is not supported by the C / C ++ memory model. (This is supported by Java 1.5 and later, and I think it is a .NET memory model.) This means that meanwhile, when the pObj == NULL check is performed and when the lock (mutex) is obtained, pObj may already have assigned to another thread. Switching flows occurs whenever the OS wants this, and not between the "lines" of the program (which do not matter after compilation in most languages).

Also, as Matt admits, he uses int as a lock, not an OS primitive. Do not do this. Proper locks require the use of memory protection instructions that are potentially associated with a line cache, etc .; use operating system primitives to lock. This is especially important because the primitives used can vary between the individual lines of the CPU that your operating system runs on; what works on CPU Foo may not work on CPU Foo2. Most operating systems either support POSIX threads (pthreads) or offer them as a wrapper for the OS streaming package, so it is often best to illustrate examples using them.

If your operating system offers the appropriate primitives, and if you absolutely need it for performance, instead of doing this type of lock / initialization, you can use the atomic comparison and swap operation to initialize a common global variable. Essentially, what you write will look like this:

 MySingleton *MySingleton::GetSingleton() { if (pObj == NULL) { // create a temporary instance of the singleton MySingleton *temp = new MySingleton(); if (OSAtomicCompareAndSwapPtrBarrier(NULL, temp, &pObj) == false) { // if the swap didn't take place, delete the temporary instance delete temp; } } return pObj; } 

This only works if it is safe to create multiple instances of your singleton (one per thread, which calls GetSingleton () at the same time) and then discards additional data. The OSAtomicCompareAndSwapPtrBarrier function provided on Mac OS X — most operating systems provide a similar primitive — checks to see if pObj NULL and only actually sets it to temp , if so. It uses hardware support to really, literally just perform an exchange and say if it happened.

Another tool to use if your OS suggests that between these two extremes is pthread_once . This allows you to configure a function that runs only once - basically, doing all the locks / barriers / etc. the trick for you - no matter how many times it gets called or how many threads it gets called.

+13
Aug 09 '08 at 23:09
source share

Here's a very simple lazy-built singleton getter:

 Singleton *Singleton::self() { static Singleton instance; return &instance; } 

This is lazy, and the following C ++ standard (C ++ 0x) requires it to be thread safe. In fact, I believe that at least g ++ implements this in a thread-safe manner. So if your target compiler or if you use a compiler that also implements this in streaming mode (maybe the new Visual Studio compilers do? I don’t know), then that may be all you need.

Also see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2513.html in this section.

+10
May 19 '10 at 16:20
source share

You cannot do this without any static variables, however, if you are willing to endure one thing, you can use Boost.Thread for this purpose. Read the one-time initialization section for more information.

Then, in your unicast access function, use boost::call_once to create the object and return it.

+8
Aug 10 '08 at 4:13
source share

For gcc, it's pretty simple:

 LazyType* GetMyLazyGlobal() { static const LazyType* instance = new LazyType(); return instance; } 

GCC will verify that initialization is atomic. For VC ++ this is not the case .: - (

One of the main problems with this mechanism is the lack of testability: if you need to reset LazyType for a new one between tests or want to change LazyType * to MockLazyType *, you will not be able to. Given this, it is usually best to use the mutex + static pointer.

Also, perhaps on the sidelines: it is best to always avoid static non-POD types. (Pointers to POD are fine.) There are many reasons for this: as you mention, the initialization order is undefined - not the order in which the destructors are not called. Because of this, programs will crash when they try to exit; often not a big deal, but sometimes showstopper when the profiler you are trying to use requires a clean exit.

+5
Sep 16 '08 at 10:21
source share

While this question has already been given, I think there are a few more points:

  • If you want the lazy creation of a single singlet using a pointer to a dynamically distributed instance, you need to make sure that you clean it in the right place.
  • You can use Matt's solution, but you will need to use the appropriate mutex / critical section to lock and by checking "pObj == NULL" both before and after the lock. Of course, pObj should also be static;), In this case, the mutex will be unnecessarily heavy, it is better to go to the critical section.

But, as already mentioned, you cannot guarantee thread-safe lazy initialization without using at least one synchronization primitive.

Edit: Jupe Derek, you're right. Guilty. :)

+1
Aug 10 '08 at 4:34
source share

You can use Matt's solution, but you will need to use the appropriate mutex / critical section to lock and by checking "pObj == NULL" both before and after the lock. Of course, pObj also had to be static;). In this case, the mutex will be too heavy, it is better to go to the critical section.

OJ, this does not work. As Chris noted, this is a double-check lock that is not guaranteed to work in the current C ++ standard. See: C ++ and the dangers of double-check locking

Edit: No problem, OJ. It is really nice in the languages ​​where it works. I expect it to work in C ++ 0x (although I'm not sure) because it is such a convenient idiom.

+1
Aug 10 '08 at 5:11
source share
  • read the weak memory model. It can break dual control locks and spindle blocks. Intel is a strong memory model (for now), so Intel is easier

  • carefully use "volatile" to avoid caching parts of the object in the register, otherwise you initialize the object pointer, but not the object itself, and another thread will crash

  • the initialization order of static variables compared to loading shared code is sometimes not trivial. I have seen cases where the code for destroying an object has already been unloaded, so the program crashed on exit

  • such objects are difficult to destroy correctly

In general, singles are hard to do right and hard to debug. It is better to avoid them at all.

+1
Nov 09 '09 at 17:32
source share

I believe that do not do this because it is not safe and will probably break more often than just initializing this material in main() , it will not be so popular.

(And yes, I know this means that you shouldn't try to do interesting things in global object constructors. That's the point.)

0
Aug 20 '08 at 16:13
source share

@Mat

I believe that do not do this, because it is unsafe and will probably break more often than just initializing this material in main (), it will not be so popular.

[And yes, I know this means that you should not try to do interesting things in global object constructors. This is the point.]

Msn

He is not trying to do anything interesting in the global constructor of objects. He tries not to do anything interesting by delaying initialization to the end (lazy construction). Initializing a variable to 0 and initializing a mutex with a static initializer is not considered interesting, because these are both things the compiler can do.

-one
Aug 20 '08 at 16:45
source share



All Articles