Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread-safe initialization of function-local static const objects

This question made me question a practice I had been following for years.

For thread-safe initialization of function-local static const objects I protect the actual construction of the object, but not the initialization of the function-local reference referring to it. Something like this:

namespace {
  const some_type& create_const_thingy()
  {
     lock my_lock(some_mutex);
     static const some_type the_const_thingy;
     return the_const_thingy;
  }
}

void use_const_thingy()
{
  static const some_type& the_const_thingy = create_const_thingy();

  // use the_const_thingy

}

The idea is that locking takes time, and if the reference is overwritten by several threads, it won't matter.

I'd be interested if this is

  1. safe enough in practice?
  2. safe according to The Rules? (I know, the current standard doesn't even know what "concurrency" is, but what about trampling over an already initialized reference? And do other standards, like POSIX, have something to say that's relevant to this?)

The reason I want to know this is that I want to know whether I can leave the code as it is or whether I need to go back and fix this.


For the inquiring minds:

Many such function-local static const objects I used are maps which are initialized from const arrays upon first use and used for lookup. For example, I have a few XML parsers where tag name strings are mapped to enum values, so I could later switch over the tags' enum values.


Since I got some answers as to what to do instead, but haven't got an answer to my actual questions (see 1. and 2. above), I'll start a bounty on this. Again:
I am not interested in what I could do instead, I do really want to know about this.

like image 470
sbi Avatar asked Jun 02 '10 08:06

sbi


People also ask

Is static initialization thread safe?

Only initialization is thread-safe. Use and modification of static local variables by multiple threads must still be manually synchronized.

Are local static variables thread safe?

With C++11, static variables with block scope have an additional guarantee; they will be initialized in a thread-safe way.

Is static thread safe?

A data type or static method is threadsafe if it behaves correctly when used from multiple threads, regardless of how those threads are executed, and without demanding additional coordination from the calling code.

How do you initialize a thread in C++?

To start a thread we simply need to create a new thread object and pass the executing code to be called (i.e, a callable object) into the constructor of the object. Once the object is created a new thread is launched which will execute the code specified in callable.


3 Answers

This is my second attempt at an answer. I'll only answer the first of your questions:

  1. safe enough in practice?

No. As you're stating yourself you're only ensuring that the object creation is protected, not the initialization of the reference to the object.

In absence of a C++98 memory model and no explicit statements from the compiler vendor, there are no guarantees that writing to the memory representing the actual reference and the writing to the memory that holds the value of the initialization flag (if that is how it is implemented) for the reference are seen in the same order from multiple threads.

As you also say, overwriting the reference several times with the same value should make no semantic difference (even in the presence of word tearing, which is generally unlikely and perhaps even impossible on your processor architecture) but there's one case where it matters: When more than one thread races to call the function for the first time during program execution. In this case it is possible for one or more of these threads to see the initialization flag being set before the actual reference is initialized.

You have a latent bug in your program and you need to fix it. As for optimizations I'm sure there are many besides using the double-checked locking pattern.

like image 105
rjnilsson Avatar answered Sep 22 '22 18:09

rjnilsson


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.

like image 24
Nikko Avatar answered Sep 20 '22 18:09

Nikko


So, the relevant part of the spec is 6.7/4:

An implementation is permitted to perform early initialization of other local objects with static storage duration under the same conditions that an implementation is permitted to statically initialize an object with static storage duration in namespace scope (3.6.2). Otherwise such an object is initialized the first time control passes through its declaration; such an object is considered initialized upon the completion of its initialization.

Assuming the second part holds (object is initialized the first time control passes through its declaration), your code can be considered thread safe.

Reading through 3.6.2, it appears the early initialization permitted is converting dynamic-initialization to static-initialization. Since static-initialization must happen before any dynamic-initialization and since I can't think of any way to create a thread until you get to dynamic-initialization, such an early initialization would also guarantee the constructor would get called a single time.

Update

So, in respect to calling the some_type constructor for the_const_thingy, your code is correct according to the rules.

This leaves the issue about overwriting the reference which is definitely not covered by the spec. That said, if you are willing to assume that references are implemented via pointers (which I believe is the most common way to do that), then all you are going to do is overwrite a pointer with the value that it already holds. So my take is that this should be safe in practice.

like image 21
R Samuel Klatchko Avatar answered Sep 24 '22 18:09

R Samuel Klatchko