Letter to the Editor

Letter to the Editor

By Alexander Nasonov

Overload, 15(77):, February 2007


Alexander Nasonov writes more on singleton.

Hi Alan,

I posted a reference to the Overload 76 to the C++ forum of Russian Software Developer Network ( http://www.rsdn.ru ) and I have had a few replies regarding my article. Since the article has not been reviewed, these comments can be considered as postmortem peer reviews.

An anonymous reader replied (translated from Russian):

Why did you say nothing about the most popular Meyers singleton where the LOCAL static variable is being used? You showed 2 most awful realizations, which nobody uses (I hope!) and it makes little sense to speak about them. I do not see any advantages of your realization over the Meyers singleton.

I agree that I should have discussed the Meyers singleton in the article. Single-threaded implementation is very simple and it manages dependencies automatically:

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

But it is not so simple in a multithreaded program. Although the C++ standard does not define the term 'thread', my experience says that, if main() is already entered, a thread safety is often guaranteed for static objects at namespace scope but not for local static variables. As a result, the inst object may be initialized more than once if two threads call the instance() simultaneously.

A naive modification of the code above:

 mutex mtx;
 Singleton& intance()
 {
   lock l(mtx); // lock mtx now, unlock in dtor
   static Singleton inst;
   return inst;
 }
    

would break a dependency tracking because now the instance() can't be called before the mtx is initialized. On POSIX platforms, it can be fixed by using PTHREAD_MUTEX_INITIALIZER to initialize the mtx object at static phase:

 pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
 Singleton& intance()
 {
   lock l(mtx); // lock mtx now, unlock in dtor
   static Singleton inst;
   return inst;
 }
    

Unfortunately, a static initializer for CRITICAL_SECTION is not available on Windows. One has to write a wrapper but it's not a trivial task.

This code can be optimized further using double locking technique but you should be very careful. Refer to [DCLocking] from the references section of the article.

Another anonymous reader wrote a test program to check a thread safety of the Meyers singleton:

 template <typename T>
 class singleton_meyers
 {
 public:
   static T& instance()
   {
     static T obj;
     std::cout << "instance finished\n" ;;
     return obj;
   }
 };

 struct sleep_in_ctor
 {
   sleep_in_ctor()
   {
     std::cout << "ctor started\n";
   ;    sleep(5);
     std::cout << "ctor finished\n";
    }
 };

 void stupid_func()
 {
   std::cout << "stupid func\n";
   singleton_meyers<sleep_in_ctor> tmp;
   tmp.instance();
 }

 int main()
 {
   boost::thread thrd1(stupid_func);
   boost::thread thrd2(stupid_func);
   thrd1.join();
   thrd2.join();
 }
 

Before doing thread-safety analysis, I'd like to note that this program uses I/O ( cout ) and process scheduling calls ( sleep ). In general, these calls should be avoided in tests that try to detect race conditions. The output is differ depending on the version of gcc it is compiled with.

Compiled with gcc 4.1 Compiled with gcc 3.4
 stupid func
   ctor started
   stupid func
     <<< 5 sec pause >>>
   ctor finished
   instance finished
   instance finished
  	
 stupid func
   ctor started
   stupid func
   ctor started
     <<< 5 sec pause >>>
   ctor finished
   instance finished
   ctor finished
   instance finished

As you see, gcc 4.1 correctly initializes the instance while gcc 3.4 incorrectly initializes two instances. Starting from version 4.0, gcc supports one-time construction API: http://www.codesourcery.com/cxx-abi/abi.html#once-ctor.

It is on by default but you can disable it with -fno-threadsafe-statics option. Note that it's not a portable extension and you shouldn't rely on it, though it's worth trying it out to detect recursive initialization (refer to 6.7 [stmt.dcl], bullet 4: If control re-enters the declaration (recursively) while the object is being initialized, the behaviour is undefined).

To summarize the reviews, I missed one important case which can be used in multithreaded programs if code is written properly, though it may be slower than a solution presented in the article because synchronization is required.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.