views:

128

answers:

4

Consider the following scenario. We have a C++ function with a static local variable:

void function()
{
    static int variable = obtain();
    //blahblablah
}

the function needs to be called from multiple threads concurrently, so we add a critical section to avoid concurrent access to the static local:

void functionThreadSafe()
{
    CriticalSectionLockClass lock( criticalSection );
    static int variable = obtain();
    //blahblablah
}

but will this be enough? I mean there's some magic that makes the variable being initialized no more than once. So there's some service data maintained by the runtime that indicates whether each static local has already been initialized.

Will the critical section in the above code protect that service data as well? Is any extra protection required for this scenario?

A: 

To avoid the locking in any case, you can go with this:

void functionThreadSafe()
{
    static int *variable = 0;
    if (variable == 0)
    {
       CriticalSectionLockClass lock( criticalSection );
       // Double check for initialization in different thread
       if (variable == 0)
       {
          variable = new int(obtain());
       }
    }
    //blahblablah
}
jopa
Now you have another static local. What changed compared to the code in question?
sharptooth
You don't need to lock at every call to the function.
jopa
That's cool, but it doesn't address thread-safety of the service data used for maintaining the static local.
sharptooth
This is not thread-safe because `variable` is not atomic. Consider one possible scenario : One thread initializes `variable`, but `variable` is not propagated through all caches, which means that one thread could see it NULL even after it was initialized by another. Thus, it could be initialized more than once, even with the critical section.
paercebal
Do you mean the value of obtain() is different for each thread?Then you have to make variable thread static. There are different ways to do this (depending on platform/compiler). But i would prefer a wrapper like boost::threads or ACE.
jopa
@jopa: No, I mean there's some service data that controls whether the variable is already initialized and accessing that data might be not thread-safe.
sharptooth
@paercebal You are right (for details, see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html). The singleton approach shown on this page should solve this.
jopa
A critical section will provide a memory barrier and ensure cache coherency, thus `variable` should be propagated correctly. Another question is whether the initializing to NULL is done atomically/or up front - e.g. does `variable` live in a zero initialized section and doesn't need code to initalize it to 0. (.i.e. can you end up with thread 1 initializes `variable = 0` the first time the function is called while thread 2 does the same but gets into a race condition because of the unprotected housekeeping the compiler generated code does ?)
nos
@sharptooth If you assign the value of the variable==0 expression to a local (non-static) bool variable, you have a marker whether the variable has been initialized.
jopa
@jopa: see http://www.drdobbs.com/184405726 where a code similar to your own is considered flawed both because the variable is not atomic/volatile, and because of possible instruction reordering (see example 3)
paercebal
@jopa: Yes, but how can I be sure that the local static has been initialized in thread-safe manner?
sharptooth
A: 

Some lateral dodges you can try that might solve your underlying problem:

  • you could make int variable be a thread-local static, if the different threads don't actually need to share the value of this variable or pass data to each other through it.
  • for an int on x86, you can use an atomic read/write such as by InterlockedCompareExchange() or its equivalent on your platform. This lets multiple threads safely access the variable without locks. It only works for hardware-native atomic types, though (eg, 32-bit and 64-bit words). You will also have to figure out what to do if two threads want to write to the variable at the same time (ie, one will discover that the other has written to it when it performs the compare-and-swap op).
Crashworks
+4  A: 

C++ says that your static variable should only be initialized once - however C++ doesn't deal with threads(yet).

gcc(atleast on *nix systems) does the proper magic to safely guard multiple threads initializing such a static variable. According to http://social.msdn.microsoft.com/Forums/en/vcgeneral/thread/12f8e2c7-d94d-4904-8c79-a0b0d1088e0b , msvc does not - and in such a case you'll have to lock the initialization yourself.

Guarding the initialization with a critical section should protect all this - i.e. your functionThreadSafe() is ok - (unless obtain() itself calls functionThreadSafe()

http://blogs.msdn.com/b/oldnewthing/archive/2004/03/08/85901.aspx is worth a read in this regard.

Personally, to avoid surprises I'd try to rewrite this so you can initialize variable yourself, once, before you create any threads - e.g.

static int variable = 0;
void init_variable() //call this once, at the start of main()
{
  variable = obtain();
}

void function() 
{
  //use variable, lock if you write to it
}
nos
+1  A: 

(I have post this on another question, but it is also an answer to this one)

Here is my take (if really you can't initialize it before threads are launched):

I've seen (and used) something like this to protect static initialization, using boost::once

#include <boost/thread/once.hpp>

boost::once_flag flag;

// get thingy
const Thingy & get()
{
    static Thingy thingy;

    return thingy;
}

// create function
void create()
{
     get();
}

void use()
{
    // Ensure only one thread get to create first before all other
    boost::call_once( &create, flag );

    // get a constructed thingy
    const Thingy & thingy = get(); 

    // use it
    thingy.etc..()          
}

In my understanding, this way all threads wait on boost::call_once except one that will create the static variable. It will be created only once and then will never be called again. And then you have no lock any more.

Nikko