Question

In my C++ code I'm keeping a pointer to an object which should be created lazily, i.e., created only upon request. I have following code, which is clearly not thread-safe.

LAZY* get_lazy()
{
    if (0 == _lazy)
        _lazy = create_lazy();
    return _lazy;
}

I wonder what kind of synchronization should I use here? I know Boost.thread provides supports for one-time initialization. But I'm hoping that there is a simple solution using TBB + C++ only. I should also note that...

  • I cannot create _lazy as a static object (I actually want to keep an unbounded array of such lazily created objects)
  • Such LAZY objects cannot be over-allocated (creation is very expensive)
Was it helpful?

Solution

You need a local mutex (tbb::mutex), to be sure you create your lazy object only once.

#include <tbb/mutex.h>

tbb::atomic<LAZY*> _lazy;
tbb::mutex myMutex;

LAZY* GetLazy()
{
  if (0 == _lazy)
  {
    myMutex.lock();
    if (0 == _lazy)
        _lazy = create_lazy();
    myMutex.unlock();
  }
  return _lazy;
}

OTHER TIPS

Is it okay to occasionally call create_lazy more than once? If so this is a very lightweight, efficient solution using only TBB:

tbb::atomic<LAZY*> lazy;

if(!lazy)
{
    LAZY *newlazy = create_lazy();

    if(lazy.compare_and_swap(newlazy, 0))
    {
        // lazy was initialized elsewhere.
        delete newlazy;
    }
}

// use lazy.

This will have much less (zero!) overhead than Maciej's solution, but again will only work if it's okay to occasionally call create_lazy more than once in the event that there is contention among threads on that specific variable.

One way to avoid both a mutex and calling create_lazy more than once is to use a spin loop. This will use more CPU than a mutex if there is contention, but will still be low overhead:

tbb::atomic<LAZY*> lazy;
static int sentry;

if(!lazy && !lazy.compare_exchange((LAZY*)&sentry, 0))
{
    // lazy is set to a sentry value while being allocated.
    try{ lazy = create_lazy(); }
    catch(...) { lazy = 0; throw; }
}
else
{
    // yield the thread while lazy is still set to the sentry.
    while(lazy == (LAZY*)&sentry)
    {
        tbb::this_tbb_thread::yield();
    }
}

// use lazy.

You might also look at how this problem is solved internally in TBB. The name to search for in the code is atomic_do_once; it's an internal (at the moment of writing) TBB function for lazy initialization. The definition of this function and auxiliary stuff is in src/tbb_misc.h, and there are a few places in other files where it is used.

The basic idea is the same as in @CoryNelson's answer, but generalized with the help of a tri-state flag (see enum do_once_state). One needs to create a static variable of type tbb::atomic<do_once_state>, and pass it, together with a function/functor that should be run once, into a call to atomic_do_once. For example:

void initialize_once();
static tbb::atomic<tbb::internal::do_once_state> init_state;
/*...*/
// Safe to execute concurrently
tbb::internal::atomic_do_once( &initialize_once, init_state );

For long-running initialization, using tbb::mutex like recommended by @MaciejDopieralski is preferred, as it avoids excessive CPU usage by putting waiting thread(s) to sleep. Note that most other mutex flavors in TBB also spin, not sleep.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top