10

I've been looking all around SO and MSDN for an answer to this question, but cannot seem to find a clear and final answer...

I know that it's in the C++11 standard and that current GCC version behave this way, but does VC2010 currently guarantees thread-safety of a local static variable initialization?

i.e.: Is this thread-safe with VC2010?

    static S& getInstance()
    {
        static S instance;
        return instance;
    }

...And if not, what is the current best practice to get a thread-safe singleton implementation in C++ with VC2010?

EDIT: As pointed out by Chris Betti's answer, VC2010 doesn't implement thread-safeness of local static variable init.

Matt Fortier
  • 1,213
  • 1
  • 10
  • 18
  • See http://stackoverflow.com/questions/164496/how-can-i-create-a-thread-safe-singleton-pattern-in-windows – MerickOWA May 14 '12 at 15:25
  • @MerickOWA : This dates back to '08 and doesn't provide a clear and globally accepted answer. Plus it doesn't even cover VC2010 (due to topic age). – Matt Fortier May 14 '12 at 15:32
  • @IC3M4N VS2010 was implemented before C++11 came out, If it doesn't implement thread safe construction of static local variables, then you're left with using techniques which have been around for many years. I don't see anything which doesn't apply to VS2010 – MerickOWA May 14 '12 at 15:38
  • @IC3M4N to be clear, Chris's answer gives link to good general answers, I was just trying to provide a link to more windows specific answers to the question. – MerickOWA May 14 '12 at 15:41
  • @MerickOWA I was just pointing out that it's not exactly what I was looking for. Thanks for your help though, it's appreciated. – Matt Fortier May 14 '12 at 20:23

2 Answers2

11

From Visual Studio 2010's documentation on Static:

Assigning a value to a static local variable in a multithreaded application is not thread safe and we do not recommend it as a programming practice.

The second part of your question has some good existing answers.

Updated Nov 22, 2015:

Others have verified, specifically, that static initialization is not thread safe either (see comment and other answer).

User squelart on VS2015:

you may want to add that VS2015 finally gets it right: https://msdn.microsoft.com/en-au/library/hh567368.aspx#concurrencytable ("Magic statics")

Community
  • 1
  • 1
Chris Betti
  • 2,721
  • 2
  • 27
  • 36
  • 6
    Agreed that c++11 requires it to be thread safe, but that does not mean VS2010 implements this requirement. According to their documentation, the requirement is not met. – Chris Betti May 14 '12 at 15:23
  • @Jagannath As said in question, I know that. I was asking about VC2010 implementation of this particular C++11 feature. – Matt Fortier May 14 '12 at 15:35
  • @ChrisBetti Thanks, exactly the kind of clear answer I was looking for! – Matt Fortier May 14 '12 at 15:35
  • 6
    This does not imply that *construction* of said is not safe. Only assignment. – Puppy May 16 '12 at 01:05
  • 1
    @DeadMG : Good point, but in all honesty it's safe to assume that if assignation is not thread-safe, initialization also isn't. – Matt Fortier May 16 '12 at 18:03
  • downvoter, is there a reason, or some way I can improve this answer? – Chris Betti Nov 11 '15 at 19:02
  • Chris: As Puppy said, the VS statement is ambiguous, as it discusses "assigning" but not explicitly "initializing", hence my down-voting. However after further investigation (actually looking at the generated assembly), I can confirm that initialization is *not* thread-safe, so your answer is in fact correct. I've tried to change my vote but it's too late. Please update your answer to explicitly confirm that initialization is indeed not thread-safe, and I should be able to up-vote it. ;-) – squelart Nov 22 '15 at 07:56
  • Also, you may want to add that VS2015 finally gets it right: https://msdn.microsoft.com/en-au/library/hh567368.aspx#concurrencytable ("Magic statics") – squelart Nov 22 '15 at 08:04
  • That particular table is good because it clearly documents that none of 2010, 2012, 2013 have it implemented, 2015 is the first version that does. – Ben Voigt Nov 22 '15 at 14:29
  • [This page](https://msdn.microsoft.com/en-us/library/y5f6w579.aspx) calls out that initialization and assignment most certainly do NOT work the same in C++11. – Ben Voigt Nov 22 '15 at 14:31
  • My comment about that is intended to support Matt F.s statement, which I believe to be true of all major c++ compilers. Can you suggest how I might reword this paragraph? – Chris Betti Nov 22 '15 at 14:41
7

The following code snippet shows "locally scoped static object initialisation" is NOT thread-safe:

#include <windows.h>
#include <stdio.h>
#include <process.h>
struct X {
    ~X() { puts("~X()"); }
    int i_ ;
    void print(void) {
        printf("thread id=%u, i = %d\n", GetCurrentThreadId(), i_);
    }
    X(int i) {
        puts("begin to sleep 10 seconds");
        Sleep(1000 * 10);
        i_ = i;
        printf("X(int) i = %d\n", i_);
        puts("end");
    }
};

X & getX()
{
    static X static_x(1000);
    return static_x;
}

void thread_proc(void *)
{
    X & x = getX();
    x.print();
}

int main(int argc, char *argv[])
{
    HANDLE all_threads[2] = {};
    all_threads[0] = HANDLE( _beginthread(thread_proc, 0, 0) );
    printf("First thread Id: %u\n", GetThreadId(all_threads[0]) );
    Sleep(1000);
    all_threads[1] = HANDLE( _beginthread(thread_proc, 0, 0) );
    printf("Second thread Id: %u\n", GetThreadId(all_threads[1]) );
    WaitForMultipleObjects( _countof(all_threads), all_threads, TRUE, 1000 * 20);
    puts("main exit");
    return 0;
}

The output will be(of course thread id will be different on your machine):

First thread Id: 20104
begin to sleep 10 seconds
Second thread Id: 20248
thread id=20248, i = 0
X(int) i = 4247392
end
thread id=20104, i = 1000
main exit
~X()

Before the first thread returns which means the singleton's ctor is called and returned, the second thread get the un-initialized object and call it's member method(because the static object is in BSS segment, it'll be initilized to zero after loader load the executable) and get the wrong value: 0.

Turning on assembly listing by /FAsc /Fastatic.asm will get the assembly code for function getX():

01:  ?getX@@YAAAUX@@XZ PROC                 ; getX
02:  
03:  ; 20   : {
04:  
05:    00000    55       push    ebp
06:    00001    8b ec        mov     ebp, esp
07:  
08:  ; 21   :   static X static_x(1000);
09:  
10:    00003    a1 00 00 00 00   mov     eax, DWORD PTR ?$S1@?1??getX@@YAAAUX@@XZ@4IA
11:    00008    83 e0 01     and     eax, 1
12:    0000b    75 2b        jne     SHORT $LN1@getX
13:    0000d    8b 0d 00 00 00
14:     00       mov     ecx, DWORD PTR ?$S1@?1??getX@@YAAAUX@@XZ@4IA
15:    00013    83 c9 01     or  ecx, 1
16:    00016    89 0d 00 00 00
17:     00       mov     DWORD PTR ?$S1@?1??getX@@YAAAUX@@XZ@4IA, ecx
18:    0001c    68 e8 03 00 00   push    1000           ; 000003e8H
19:    00021    b9 00 00 00 00   mov     ecx, OFFSET ?static_x@?1??getX@@YAAAUX@@XZ@4U2@A
20:    00026    e8 00 00 00 00   call    ??0X@@QAE@H@Z      ; X::X
21:    0002b    68 00 00 00 00   push    OFFSET ??__Fstatic_x@?1??getX@@YAAAUX@@XZ@YAXXZ ; `getX'::`2'::`dynamic atexit destructor for 'static_x''
22:    00030    e8 00 00 00 00   call    _atexit
23:    00035    83 c4 04     add     esp, 4
24:  $LN1@getX:
25:  
26:  ; 22   :   return static_x;
27:  
28:    00038    b8 00 00 00 00   mov     eax, OFFSET ?static_x@?1??getX@@YAAAUX@@XZ@4U2@A
29:  
30:  ; 23   : }

At line 10 the cryptic symbol [?$S1@?1??getX@@YAAAUX@@XZ@4IA] is the global indicator(also in BSS) which flags whether the singleton is ctored or not, it will be flaged as true by line 14-17, just before calling into the ctor, that's the problem, this also explains why the second thread immediately got the un-initialized singleton object and happily call it's member function. There's no thread-safety related code inserted by the compiler.

zhaorufei
  • 2,045
  • 19
  • 18