Sunday, 2 December 2007

You lazy bastard, Part 1.

Or "Portable, thread-safe, lazily initialized singletons with Boost."

Portable, efficient, lazily initialized thread-safe singletons are something that are needed fairly often in the C++ wild. I don't intend to cover why you are insane for wanting this. I intend to cover a naive solution that should work, but doesn't, and another solution that should work, given my information. I believe this solution addresses three of the four characteristics, namely efficiency is dropped. I prefer correctness and programmer time is important (and good programmers are expensive!)

To cover why you are insane for wanting these characteristics in the first place, I refer you to this and this.

Now, you may ask what makes me so special as to pretend to be an authority on this. Let me clarify: I don't pretend to be an authority. I am not. I have walked through this minefield a little bit and I will mark the mines for you as best I can. But if I may boast a little bit: I did find a bug in Open Solaris's pthread_once implementation (on x86) and another thread-safety issue in Boost Serialization. The reason I point these bugs out is not that the authors were deficient or incompetent. They are quite the opposite. I point these out because I feel that people don't realize what things are waiting to bite them in the butt when it comes to this area of software development. Indeed, it is easy to point the finger "haha, you made a bug," but we don't like those people around here. I have a shotgun and some bullets reserved for you if you insist on staying. Anyway, if our experts can make this mistake, you have made it and you don't even know about it.

If by now you haven't read through the above linked pages, I suggest you do so now. I don't have the writing capacity to not blather on like an idiot and repeat them to you. Still here? Go! Use the tabbed feature of your browser. If your browser doesn't have tabs, I have another box of bullets for you!

Done? Good.

So, quite often, we think we want a single instance of some object but we don't want the object to be constructed on program startup. The simplest way to do ensure this is the following:


MyObject & seductive()
{
static MyObject t;
return t;
}

That is a very seductive pattern, and if you are not concerned about thread-safety (i.e., you don't have multiple threads), then you are done. You may go Google Britney Spears. This is also known as Myers's singleton. Don't know which Myers. Don't actually care :-)

Now, what happens when multiple threads enter seductive() at the same time? You guessed it, you can get multiple initializations and all sorts of bad stuff happening. If you didn't guess that, then you definitely didn't read this.

All right. What the fudge? What are we supposed to do?!! I say: drop optimal efficiency as a requirement. You are probably copying a huge vector somewhere anyway before writing it to a file. That opens up many new worlds for solving this problem. The most straightforward, using Boost Threads functionality (it won't compile, I'm sure):

static boost::mutex mtx;
MyObject & looks_sexy_but_isnt()
{
scoped_lock lock(mtx);
static MyObject t;
return t;
}

Even if it does compile, it won't work. "Hang on," you say. "That will work just fine. You're crazy. I'm going to go Google Britney Spears." You are wrong. That will not work. It will only appear to work kind of most of the time. Why? And the reason is:

Boost Mutex is not statically initializable

What the heck does that mean? The details are a bit fuzzy in my head, but the bottom line is that the C++ standard does not guarantee that the mtx variable is initialized before main(). Essentially, if a datatype has a constructor, you are SOL. ES. OH. EL. I don't know why the heck they didn't make the mutex statically initializable, but I guess it starts with "Win" and ends with "dows". I know pthread mutexes are statically initializable by design.

So what the fudge? We are still screwed. Yep. Pretty much.

So let us recap our problems:

  • If we use a mutex, it must be statically initializable

  • Double checked locking is broken except when the moon is blue and you are standing on your tippy toes

  • Popcorn gives me gas


If we decide to use Boost Threads (a fine choice, but sometimes limited), it has the first problem and there is no general, portable solution to the second. The third involves less popcorn.

Ah, but there is a silver lining. A couple of years back, I was at SD West when Scott Myers was giving his "Double checked locking (DCL) is broken" talk. Most of the crowd had no flipping idea what DCL was (I sure didn't) but Myers has this ability to communicate that I envy. So by the end of the talk, everyone knew what he was talking about and people were discussing ways to make it work. David Abrahams, of Boost fame, said: "Why don't you just use pthread_once?" And I thought: "Duh!" So the idea is not mine, but the implementation is! I present to you "Captain Sabraham's (or Dohail's) Singleton" :-)

#include <boost/thread/once.hpp>

template<typename T>
struct singleton : private boost::noncopyable
{
public:
static
T & instance()
{
boost::call_once(call_me_once,once_);
return instance_helper();
}
private:
static boost::once_flag once_;
static void call_me_once()
{
instance_helper();
}

static T & instance_helper()
{
static T t;
return t;
}
singleton();
~singleton();
};
template<typename T>
boost::once_flag singleton<T>::once_ = BOOST_ONCE_INIT;

boost::once_flag is statically initialized with BOOST_ONCE_INIT.

Now we did drop efficiency, and that makes you very very sad, Mr Premature Optimizer. There is good news and bad news. The bad news is that the Boost Thread implementation of boost::call_once is slow on Windows. The good news is that it is fast on everything else.

Oh by the way, I lied. There is no part 2.

Disclaimer: This code may not work at all. You are free to not use it.

2 comments:

Niteris said...

There is another work-around that works very nicely, put your static MutexWin32 inside a static function:

static MutexWin32& s_static_mutex() {
static MutexWin32 s_mutex;
return s_mutex;
}
// Make sure that it is initialized on startup. This makes it safe for static intializers.

MutexWin32& init_on_startup = s_static_mutex();

Doing this over and over again and we quickly determine that we need a macro which will create a function to get at our static mutex.

STATIC_MUTEX(s_my_mutex);

// Then use it as so:

static void ConcurrentFunction() {
ScopedLock lock(s_my_mutex());
// Thread safe code here.
}

The only way this may not work is if there are static initializers which launch multiple threads which both hit the same mutex.

However in practice launching threads in static constructors is rare and I've never seen it myself. If such a case happens then maybe BOOST_INIT_ONCE should be used, if your company allows it.

Sohail Somani said...

As far as I know, there is no C++-level guarantee that s_static_mutex() is called before main *except* if it is in the main module (object file) itself.

For other modules, it is only guaranteed to be called before any other function in that module.

Do you know why your solution is different?