赞
踩
程序在多线程执行时,会发生random crash。结果跟踪到是多线程并发调用下面函数时出现非法操作:
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
改写以后的伪代码:
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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。