【问题标题】:Is static init thread-safe with VC2010?VC2010 的静态初始化线程安全吗?
【发布时间】:2012-05-22 02:10:41
【问题描述】:

我一直在 SO 和 MSDN 上寻找这个问题的答案,但似乎找不到明确和最终的答案......

我知道它在 C++11 标准中并且当前的 GCC 版本是这样的,但是 VC2010 目前是否保证局部静态变量初始化的线程安全?

即:VC2010 的线程安全吗?

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

...如果不是,那么当前使用 VC2010 在 C++ 中实现线程安全的单例的最佳实践是什么?

编辑:正如 Chris Betti 的回答所指出的,VC2010 没有实现局部静态变量 init 的线程安全性。

【问题讨论】:

  • @MerickOWA :这可以追溯到 08 年,并没有提供明确且全球公认的答案。此外,它甚至不包括 VC2010(由于主题年龄)。
  • @IC3M4N VS2010 是在 C++11 出来之前实现的,如果它没有实现静态局部变量的线程安全构造,那么你就只能使用已经存在多年的技术了.我没有看到任何不适用于 VS2010
  • @IC3M4N 要清楚,克里斯的回答提供了良好的一般答案的链接,我只是想提供一个链接,指向更多针对该问题的 Windows 特定答案。
  • @MerickOWA 我只是指出这不是我想要的。不过感谢您的帮助,非常感谢。

标签: c++ multithreading visual-c++ thread-safety c++11


【解决方案1】:

来自 Visual Studio 2010 的 documentation on Static

在多线程应用程序中为静态局部变量赋值不是线程安全的,我们不建议将其作为编程实践。

你问题的第二部分有some good existing answers

2015 年 11 月 22 日更新:

其他人已经证实,特别是静态初始化也不是线程安全的(见评论和其他答案)。

VS2015 上的用户反响:

你可能想补充一点,VS2015 终于做对了:https://msdn.microsoft.com/en-au/library/hh567368.aspx#concurrencytable ("Magic statics")

【讨论】:

  • 同意c++11要求它是线程安全的,但这并不意味着VS2010实现了这个要求。根据他们的文档,不符合要求。
  • @Jagannath 正如问题所说,我知道。我在询问这个特定 C++11 功能的 VC2010 实现。
  • @ChrisBetti 谢谢,正是我正在寻找的明确答案!
  • 这并不意味着上述的构造是不安全的。仅分配。
  • @DeadMG :好点子,但老实说,如果分配不是线程安全的,那么初始化也不是。
【解决方案2】:

以下代码 sn-p 显示“局部范围的静态对象初始化”不是线程安全的:

#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;
}

输出将是(当然线程 ID 在您的机器上会有所不同):

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()

在第一个线程返回之前调用并返回单例的ctor,第二个线程获取未初始化的对象并调用它的成员方法(因为静态对象在BSS段中,它会在之后初始化为零loader 加载可执行文件)并得到错误的值:0。

通过 /FAsc /Fastatic.asm 打开汇编列表将获得函数 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   : }

在第 10 行,神秘符号 [?$S1@?1??getX@@YAAAUX@@XZ@4IA] 是全局指标(也在 BSS 中),它标记单例是否被连接,它将是由第 14-17 行标记为真,就在调用 ctor 之前,这就是问题所在,这也解释了为什么第二个线程立即获得未初始化的单例对象并愉快地调用它的成员函数。编译器没有插入与线程安全相关的代码。

【讨论】:

  • 今天我用VS2013检查了这个,(不幸的是)结果是一样的。
猜你喜欢
  • 2010-10-27
  • 1970-01-01
  • 1970-01-01
  • 2010-12-30
  • 2010-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-03
相关资源
最近更新 更多