Static initialization and thread safety

In a recent code review, I found some globally-accessible state that was incorrectly being initialized more than once. I suggested that the state be made truly global, and because this is C++ and because I have been bitten rather hard before, I recommended using the construct-on-first-use idiom, e.g.:

// Never invoke this method in a destructor kthx.
Foo* foo() {
    static Foo singleton;
    return &singleton;
}

Another colleague, however, wondered whether and to what extent this code is thread safe. Local variables with static storage duration are initialized on first execution in C and C++, but I’d never given much thought to the multi-threaded context, because, embarrassingly, Marshall Cline has never led me astray. Cursory googling, however, turned up disturbing evidence to the contrary. Refer to the article for details, but basically the author claims that compilers will emit thread-unsafe initialization code, and furthermore that this is “required by the C++ standard” (presumably C++03 at the time). I couldn’t double check the requirement claim (C++ standards, it turns out, are free as in speech, not beer), and despite my previous life I couldn’t bring myself to dredge up a 2004-vintage compiler and experiment directly. Besides, it’s 2012 and we’re all working with contemporary compilers (right?), so I asked the C++11 draft standard. From Section 6.7.4, which describes initialization of block scope variables with static storage duration:

Otherwise such a variable is initialized the first time control passes
through its declaration; such a variable is considered initialized upon
the completion of its initialization. If the initialization exits by
throwing an exception, the initialization is not complete, so it will be
tried again the next time control enters the declaration. If control
enters the declaration concurrently while the variable is being
initialized, the concurrent execution shall wait for completion of the
initialization.

The standard is pretty emphatically on the thread-safe side. But what do actual compilers do? Consider the following toy program:

struct Foo {
    Foo() { foo_ = 11; } 
    int foo_; 
}; 

Foo* getFoo() { 
    static Foo singleton; 
    return &singleton; 
} 

Compiling this code under GCC 4.6.1 & disassembling getFoo yields the following:

400614: push %rbp 
400615: mov %rsp,%rbp 
400618: mov $0x601040,%eax 
40061d: movzbl (%rax),%eax 
400620: test %al,%al 
400622: jne 40064b 
400624: mov $0x601040,%edi 
400629: callq 400500 <\_\_cxa_guard_acquire@plt> 
40062e: test %eax,%eax 
400630: setne %al 
400633: test %al,%al 
400635: je 40064b 
400637: mov $0x601048,%edi 
40063c: callq 400680 <\_ZN3FooC1Ev> 
400641: mov $0x601040,%edi
400646: callq 400520 <\\_\_cxa_guard_release@plt> 
40064b: mov $0x601048,%eax
400650: pop %rbp 

The constructor for block scoped static variable singleton is protected by a guard variable (*$0x601040) and within a critical section (cxa_guard_release sets the guard variable after initialization), fulfilling the requirements of the spec. Whatever the case was in 2004, block scoped statics in contemporary C++ do not pose a risk for concurrent programs. This doesn’t mean they shouldn’t be used cautiously, however: the spec leaves reentrant initialization undefined, except to require that no deadlock occur. If your initialization routines are reentrant, though, I submit that you have a more serious problem. Edit: The above discussion in no way applies to VC 2012 (and previous versions), for which assigning to a local variable with static storage duration remains manifestly unsafe, as I have recently discovered to my vast dismay.

3 thoughts on “Static initialization and thread safety

  1. Pingback: How to implement thread safe local static variable in C++03? | BlogoSfera

    • That’s true, as I mentioned above. It’s worth noting that they won’t be safe in Microsoft Visual C++ until the VS2014 release; the VS team’s late embrace of this language feature (present in GCC since I don’t even recall when) was the impetus for this post.

Comments are closed.