[C++想当然]线程不安全的local static variable

案例

程序在多线程执行时,会发生random crash。结果跟踪到是多线程并发调用下面函数时出现非法操作:

     class  ElementMap {
        
static  Type find ( const  std:: string &  name) {
            
static  ElementMap emap;
            Type type 
=  UNKNOWN;
            emap.FindType(name,type);
            
return  type;
        }
    }

ElementMap::find()函数返回一个字符串所对应的类型。其中使用一个emap对象存储name和type之间的静态映射关系,为了节省不必要的初始化以及避免污染全局变量名称,将emap实现为local static variable。

原因

当线程1第一次到达find()函数后,将进行初始化emap对象;但在初始化完成之前,线程2也到达了find(),并认为emap对象已经初始化成功,进行后续操作时程序crash。

解决

将emap变成global static variable以后程序运行正常。

分析

我们对GCC如何处理local static varible进行分析,下面是GCC在find()调用中生成的相应代码:

0x006c3673 <+13>:        jne    0x6c36ed <+135>
0x006c3675 <+15>:        mov    DWORD PTR [esp],0x807ef20
0x006c367c <+22>:        call   0x249260 <__cxa_guard_acquire>
0x006c3681 <+27>:        test   eax,eax
0x006c3683 <+29>:        je     0x6c36ed <+135>
0x006c3685 <+31>:        mov    BYTE PTR [ebp-5],0x0
0x006c3689 <+35>:        mov    DWORD PTR [esp],0x94f4f0
0x006c3690 <+42>:        call   0x6c3728 <ElementMap>
0x006c3695 <+47>:        mov    DWORD PTR [esp],0x807ef20
0x006c369c <+54>:        call   0x249330 <__cxa_guard_release>
0x006c36a1 <+59>:        mov    DWORD PTR [esp+8],0x9492a0
0x006c36a9 <+67>:        mov    DWORD PTR [esp+4],0x0
0x006c36b1 <+75>:        mov    DWORD PTR [esp],0x6c1320
0x006c36b8 <+82>:        call   0xd9f600 <__cxa_atexit_internal>
0x006c36bd <+87>:        jmp    0x6c36ed <+135>

改写以后的伪代码:

  if (obj_guard.first_byte == 0) { //如果emap为空
if ( __cxa_guard_acquire (&obj_guard) ) { //如果emap初始化未未完成
try {
... initialize the object ...;
} catch (...) {
__cxa_guard_abort (&obj_guard);
throw;
}
... queue object destructor with __cxa_atexit() ...;
__cxa_guard_release (&obj_guard);//将emap设置为已经初始化好
      __cxa_atexit_internal (&obj_guard); //将emap的析构注册到系统退出时候的调用列表中

}
}

[改自:http://www.codesourcery.com/cxx-abi/abi.html#once-ctor]
 

相比之下,Visual Studio 2003的方式就稍微粗糙点:

   static ElementMap emap;
007A8B83  mov         eax,dword ptr [`ElementMap::find'::`2'::$S1 (0F7928Ch)]
007A8B88  and         eax,1
007A8B8B  jne         ElementMap::find+6Fh (7A8BBFh)
007A8B8D  mov         eax,dword ptr [`ElementMap::find'::`2'::$S1 (0F7928Ch)]
007A8B92  or          eax,1
007A8B95  mov         dword ptr [`napa2::hir::ElementMap::find'::`2'::$S1 (0F7928Ch)],eax
007A8B9A  mov         dword ptr [ebp-4],0
007A8BA1  mov         ecx,offset emap (0F79268h)
007A8BA6  call        ElementMap::ElementMap (6585CBh)
007A8BAB  push        offset `ElementMap::find'::`2'::emap (6621F7h)
007A8BB0  call        @ILT+53000(_atexit) (65DF0Dh)
007A8BB5  add         esp,4
007A8BB8  mov         dword ptr [ebp-4],0FFFFFFFFh

 改写以后的伪代码:

ElementMap  * emap;  // 全局变量

find()
{
    
if  (emp  ==  NULL) ...{
        emp 
=   new  ElementMap();
        __atexit(emp); 
// 将ElementMap析构函数注册到系统退出时西药调用和的函数列表中
  }
...
}

 

结论

local static variable虽然在标准中确定其初始化将在在第一次进入函数前被调用,但没有强制此初始化在任意一个并发调用之前完成。而对编译器生成的代码的分析证明了我们的担心。

同时,由于每次函数调用系统都会查询local static variable是否被初始化,而且按照ABI标准对于每个local static variable,系统还会分配一个64bit的guard variable。而global static variable可以避免这些开销。

总结

避免在多线程环境下使用local static variable。

将一定会执行到的函数中的local static variable改写为global static variable可以提高程序性能。

参考

[Itanium C++ ABI] http://www.codesourcery.com/cxx-abi/abi.html

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值