在GCC中函数静态变量是线程安全函数的吗

VC和gcc不同,不能保证静态变量的线程安全性。这就给我们的程序带来了非常大的安全隐患和诸多不便。这一点应该引起我们的重视!尤其是在构造函数耗时比較长的时候。非常可能给程序带来意想不到的结果。本文从測试代码開始,逐步分析原理,最后给出解决方式。
多线程状态下。VC不能保证在使用函数的静态变量的时候,它的构造函数已经被运行完成,以下是一段測试代码:
class TestStatic
TestStatic()
Sleep(1000*10);
m_num = 999;
DWORD WINAPI TestThread( LPVOID lpParam )
static TestS
printf(&Thread[%d] Num[%d]\n&, lpParam, test.m_num);
int _tmain(int argc, _TCHAR* argv[])
DWORD dwThreadId;
for (int i=1; i&=3; i++)
CreateThread(NULL,0,TestThread,(LPVOID)i,0,&dwThreadId);
for (int i =0; i&10; i++)
測试代码有益在构造函数中制造了一个较长时间的延时。程序执行结果:
Thread[2] Num[0]
Thread[3] Num[0]
Thread[1] Num[999]
结果显示,线程2和线程3在静态变量的构造函数没有运行完成的时候就已经使用了该变量实例。于是得到了错误的结果。
从以下列出的TestThread函数的反汇编代码不难看出问题所在。静态变量实例不存在的时候。程序会生成一个实例然后调用构造函数。当实例存在的时候直接就跳过生成实例和调用构造函数两个步骤。
结合上面的输出结果,线程1最先调用函数TestThread,因此生成了实例test而且開始调用TestStatic类构造函数。构造函数卡在了sleep上。再此之后,线程2和线程3先后来调用TestThread函数。可是此时尽管构造函数没有运行完成,可是静态变量的实例已经存在,所以跳过了生成实例和调构造函数,直接来到了printf函数的调用处,输出了没有初始化的变量值(这里是0)。当sleep完成后,构造函数运行完成,变量值被设置为999,仅仅有线程1得到了正确的结果999。
&&&& static TestS
00D48A7D& mov&&&&&&&& eax,dword ptr [$S1 (0D9EA94h)]
00D48A82& and&&&&&&&& eax,1
00D48A85& jne&&&&&&&&TestThread&#43;6Ch (<span style="color:#D48AACh)
00D48A87& mov&&&&&&&& eax,dword ptr [$S1 (0D9EA94h)]
00D48A8C& or&&&&&&&&& eax,1
00D48A8F& mov&&&&&&&& dword ptr [$S1 (0D9EA94h)],eax
00D48A94& mov&&&&&&&& dword ptr [ebp-4],0
00D48A9B& mov&&&&&&&& ecx,offset test (0D9EA98h)
00D48AA0& call&&&&&&& TestStatic::TestStatic (0D2DF6Dh)
00D48AA5& mov&&&&&&&& dword ptr [ebp-4],0FFFFFFFFh
&&&& printf(&Thread[%d] Num[%d]\n&, lpParam, test.m_num);
<span style="color:#D48AAC&mov&&&&&&&& esi,esp
00D48AAE& mov&&&&&&&& eax,dword ptr [test (0D9EA98h)]
00D48AB3& push&&&&&&& eax&
00D48AB4& mov&&&&&&&& ecx,dword ptr [ebp&#43;8]
00D48AB7& push&&&&&&& ecx&
00D48AB8& push&&&&&&& offset string &thread[%d] num[%d]& (0D8A0A0h)
00D48ABD& call&&&&&&& dword ptr [&#127;MSVCR90D_NULL_THUNK_DATA (0DA0B3Ch)]
类&#20284;的代码。我们在linux上用gcc编译程序,看看效果怎样:
class TestStatic
TestStatic()
sleep(10);
m_num = 999;
static void* TestThread( void* lpParam )
static TestS
printf(&Thread[%d] Num[%d]\n&, lpParam, test.m_num);
int main (int argc, char *argv[])
pthread_attr_t ThreadA
pthread_attr_init(&ThreadAttr);
pthread_attr_setdetachstate(&ThreadAttr, PTHREAD_CREATE_DETACHED);
for (int i=1; i&=3; i++)
pthread_create(&tid, &ThreadAttr, TestThread, (void*)i);
sleep(60*60*24);
return(0);
终于的结果显示。gcc编译出的程序和VC出现不同结果,每一个线程都得到了正确的数&#20540;。可见gcc是真正保证了函数内部静态变量的线程安全性的,程序执行结果例如以下:
Thread[3] Num[999]
Thread[2] Num[999]
Thread[1] Num[999]
相同,我们从TestThread函数的反汇编代码代码来分析问题。不难看出,gcc和VC最大的差别就在于call&&0x400a50
&__cxa_guard_acquire@plt&,这一行代码。gcc在创建静态变量实例之前先要获取锁,而且构造函数运行完成才觉得实例创建成功。显然,这个锁是gcc自己主动加入上的代码。因此,构造函数没有运行完成,全部线程都不能获取到test变量。也就不会像VC程序一样输出错误的结果了。
0x40195a&&& push&& rbp
0x40195b&&& mov&&& rbp,rsp
0x40195e&&& push&& r12
0x401960&&& push&& rbx
0x401961&&& sub&&& rsp,0x10
0x401965&&& mov&&& QWORD PTR [rbp-0x18],rdi
0x401969&&& mov&&& eax,0x6031f0
0x40196e&&& movzx& eax,BYTE PTR [rax]
0x401971&&& test&& al,al
0x401973&&& jne&&&
0x4019a2 &TestThread(void*)&#43;72&
0x401975&&& mov&&& edi,0x6031f0
0x40197a&&&call&& 0x400a50
&__cxa_guard_acquire@plt&
0x40197f&&& test&& eax,eax
0x401981&&& setne& al
0x401984&&& test&& al,al
0x401986&&& je&&&& 0x4019a2 &TestThread(void*)&#43;72&
0x401988&&& mov&&& r12d,0x0
0x40198e&&& mov&&& edi,0x6031f8
0x401993&&&call&& 0x401b06 &TestStatic::TestStatic()&
0x401998&&& mov&&& edi,0x6031f0
0x40199d&&&call&& 0x400ae0 &__cxa_guard_release@plt&
<span style="color:#x4019a2&&& mov&&& edx,DWORD PTR [rip&#43;0x201850]&&&&&&& # 0x6031f8 &_ZZL10TestThreadPvE4test&
0x4019a8&&& mov&&& rax,QWORD PTR [rbp-0x18]
0x4019ac&&& mov&&& rsi,rax
0x4019af&&& mov&&& edi,0x401d9c
0x4019b4&&& mov&&& eax,0x0
0x4019b9&&& call&& 0x400a40 &printf@plt&
0x4019be &&&&&& mov&&& eax,0x0
0x4019c3 &&&&&&& add&&& rsp,0x10
0x4019c7 &&&&&&& pop&&& rbx
0x4019c8 &&&&&&& pop&&& r12
0x4019ca &&&&&&& pop&&& rbp
0x4019cb &&&&&&& ret&&&
0x4019cc &&&&&&& mov&& &rbx,rax
0x4019cf &&&&&&&& test&& r12b,r12b
0x4019d2 &&&&&& jne&&& 0x4019de &TestThread(void*)&#43;132&
0x4019d4 &&&&&& mov&&& edi,0x6031f0
0x4019d9 &&&&&& call&& 0x400b40 &__cxa_guard_abort@plt&
0x4019de &&&&&& mov&&& rax,rbx
0x4019e1 &&&&&& mov&&& rdi,rax
0x4019e4 &&&&&& call&& 0x400b70 &_Unwind_Resume@plt&
大家都喜欢使用Singleton模式。用的时候图方便,也喜欢直接在函数里面直接用个静态变量。有的时候也必须使用静态变量。比方须要在程序退出的时候运行析构函数的情况。
可是多线程状态下。VC和gcc不同。不能保证静态变量的线程安全性。VC的这个缺陷导致我们在使用Singleton模式的时候,不能像gcc一样直接採用静态函数成员变量的方式。这就给我们的程序带来了非常大的安全隐患和诸多不便。这一点应该引起我们的重视!尤其是在构造函数耗时比較长的时候。非常可能给程序带来意想不到的结果。我们必须使用变通的方法,自己来控制类的初始化过程。
曾经我在解决问题的时候就是直接定义一个全局变量的锁,可是定义全局变量代码不够美观。毕竟不是一个好的风&#26684;。同一时候,加锁解锁也相当影响效率。
以下我给出一个能够作为固定模式使用的范例代码供大家參考。基本思路就是利用函数内部的一个基本类型的变量来控制复杂实例的生成:
class ClassStatic
ClassStatic()
Sleep(1000*10);
m_num = 999;
DWORD WINAPI TestThread( LPVOID lpParam )
static volatile long single = 1;
while(single != 0)
if (1 == _InterlockedCompareExchange(&single, 2, 1))
for ( unsigned int i = 0; i & 1024; i++ )
_mm_pause();
while (single != 0)
static ClassS
single && (single = 0);
printf(&Thread[%d] Num[%d]\n&, lpParam, test.m_num);
这次的执行结果就正确了:
Thread[3] Num[999]
Thread[2] Num[999]
Thread[1] Num[999]
版权声明:本文博客原创文章,博客,未经同意,不得转载。
阅读(...) 评论()&|&&|&&|&&|&&
当前位置: >
VC和gcc在保证函数static变量线程安全性上的区别
作者:yichigo & 来源:转载 &
VC和gcc不同,不能保证静态变量的线程安全性。这就给我们的程序带来了很大的安全隐患和诸多不便。这一点应该引起我们的重视!尤其是在构造函数耗时比较长的时候,很可能给程序带来意想不到的结果。本文从测试代码开始,逐步分析原理,最后给出解决方案。 多线程状态下,VC不能保证在使用函数的静态变量的时候,它的构造函数已经被执行完毕,下面是一段测试代码: classTestStatic
VC和gcc不同,不能保证静态变量的线程安全性。这就给我们的程序带来了很大的安全隐患和诸多不便。这一点应该引起我们的重视!尤其是在构造函数耗时比较长的时候,很可能给程序带来意想不到的结果。本文从测试代码开始,逐步分析原理,最后给出解决方案。
多线程状态下,VC不能保证在使用函数的静态变量的时候,它的构造函数已经被执行完毕,下面是一段测试代码:
class TestStatic
TestStatic()
Sleep(1000*10);
m_num = 999;
DWORD WINAPI TestThread( LPVOID lpParam )
static TestS
printf("Thread[%d] Num[%d]\n", lpParam, test.m_num);
int _tmain(int argc, _TCHAR* argv[])
DWORD dwThreadId;
for (int i=1; i&=3; i++)
CreateThread(NULL,0,TestThread,(LPVOID)i,0,&dwThreadId);
for (int i =0; i&10; i++)
测试代码故意在构造函数中制造了一个较长时间的延时,程序运行结果:
Thread[2] Num[0]
Thread[3] Num[0]
Thread[1] Num[999]
结果显示,线程2和线程3在静态变量的构造函数没有执行完毕的时候就已经使用了该变量实例,于是得到了错误的结果。
从下面列出的TestThread函数的反汇编代码不难看出问题所在。静态变量实例不存在的时候,程序会生成一个实例然后调用构造函数。当实例存在的时候直接就跳过生成实例和调用构造函数两个步骤。
结合上面的输出结果,线程1最先调用函数TestThread,因此生成了实例test并且开始调用TestStatic类构造函数,构造函数卡在了sleep上。再此之后,线程2和线程3先后来调用TestThread函数,但是此时虽然构造函数没有执行完毕,但是静态变量的实例已经存在,所以跳过了生成实例和调构造函数,直接来到了printf函数的调用处,输出了没有初始化的变量值(这里是0)。当sleep完成后,构造函数执行完毕,变量值被设置为999,只有线程1得到了正确的结果999。
&&&& static TestS
00D48A7D& mov&&&&&&&& eax,dword ptr [$S1 (0D9EA94h)]
00D48A82& and&&&&&&&& eax,1
00D48A85& jne&&&&&&&&TestThread+6Ch (<span style="color:#D48AACh)
00D48A87& mov&&&&&&&& eax,dword ptr [$S1 (0D9EA94h)]
00D48A8C& or&&&&&&&&& eax,1
00D48A8F& mov&&&&&&&& dword ptr [$S1 (0D9EA94h)],eax
00D48A94& mov&&&&&&&& dword ptr [ebp-4],0
00D48A9B& mov&&&&&&&& ecx,offset test (0D9EA98h)
00D48AA0& call&&&&&&& TestStatic::TestStatic (0D2DF6Dh)
00D48AA5& mov&&&&&&&& dword ptr [ebp-4],0FFFFFFFFh
&&&& printf("Thread[%d] Num[%d]\n", lpParam, test.m_num);
<span style="color:#D48AAC&mov&&&&&&&& esi,esp
00D48AAE& mov&&&&&&&& eax,dword ptr [test (0D9EA98h)]
00D48AB3& push&&&&&&& eax&
00D48AB4& mov&&&&&&&& ecx,dword ptr [ebp+8]
00D48AB7& push&&&&&&& ecx&
00D48AB8& push&&&&&&& offset string "thread[%d] num[%d]" (0D8A0A0h)
00D48ABD& call&&&&&&& dword ptr [MSVCR90D_NULL_THUNK_DATA (0DA0B3Ch)]
类似的代码,我们在linux上用gcc编译程序,看看效果如何:
class TestStatic
TestStatic()
sleep(10);
m_num = 999;
static void* TestThread( void* lpParam )
static TestS
printf("Thread[%d] Num[%d]\n", lpParam, test.m_num);
int main (int argc, char *argv[])
pthread_attr_t ThreadA
pthread_attr_init(&ThreadAttr);
pthread_attr_setdetachstate(&ThreadAttr, PTHREAD_CREATE_DETACHED);
for (int i=1; i&=3; i++)
pthread_create(&tid, &ThreadAttr, TestThread, (void*)i);
sleep(60*60*24);
return(0);
最终的结果显示,gcc编译出的程序和VC出现不同结果,每个线程都得到了正确的数值,可见gcc是真正保证了函数内部静态变量的线程安全性的,程序运行结果如下:
Thread[3] Num[999]
Thread[2] Num[999]
Thread[1] Num[999]
同样,我们从TestThread函数的反汇编代码代码来分析问题。不难看出,gcc和VC最大的区别就在于call&&0x400a50 &__cxa_guard_acquire@plt&,这一行代码。gcc在创建静态变量实例之前先要获取锁,并且构造函数执行完毕才认为实例创建成功。显然,这个锁是gcc自动添加上的代码。因此,构造函数没有执行完毕,所有线程都不能获取到test变量,也就不会像VC程序一样输出错误的结果了。
0x40195a&&& push&& rbp
0x40195b&&& mov&&& rbp,rsp
0x40195e&&& push&& r12
0x401960&&& push&& rbx
0x401961&&& sub&&& rsp,0x10
0x401965&&& mov&&& QWORD PTR [rbp-0x18],rdi
0x401969&&& mov&&& eax,0x6031f0
0x40196e&&& movzx& eax,BYTE PTR [rax]
0x401971&&& test&& al,al
0x401973&&& jne&&&
0x4019a2 &TestThread(void*)+72&
0x401975&&& mov&&& edi,0x6031f0
0x40197a&&&call&& 0x400a50 &__cxa_guard_acquire@plt&
0x40197f&&& test&& eax,eax
0x401981&&& setne& al
0x401984&&& test&& al,al
0x401986&&& je&&&& 0x4019a2 &TestThread(void*)+72&
0x401988&&& mov&&& r12d,0x0
0x40198e&&& mov&&& edi,0x6031f8
0x401993&&&call&& 0x401b06 &TestStatic::TestStatic()&
0x401998&&& mov&&& edi,0x6031f0
0x40199d&&&call&& 0x400ae0 &__cxa_guard_release@plt&
<span style="color:#x4019a2&&& mov&&& edx,DWORD PTR [rip+0x201850]&&&&&&& # 0x6031f8 &_ZZL10TestThreadPvE4test&
0x4019a8&&& mov&&& rax,QWORD PTR [rbp-0x18]
0x4019ac&&& mov&&& rsi,rax
0x4019af&&& mov&&& edi,0x401d9c
0x4019b4&&& mov&&& eax,0x0
0x4019b9&&& call&& 0x400a40 &printf@plt&
0x4019be &&&&&& mov&&& eax,0x0
0x4019c3 &&&&&&& add&&& rsp,0x10
0x4019c7 &&&&&&& pop&&& rbx
0x4019c8 &&&&&&& pop&&& r12
0x4019ca &&&&&&& pop&&& rbp
0x4019cb &&&&&&& ret&&&
0x4019cc &&&&&&& mov&& &rbx,rax
0x4019cf &&&&&&&& test&& r12b,r12b
0x4019d2 &&&&&& jne&&& 0x4019de &TestThread(void*)+132&
0x4019d4 &&&&&& mov&&& edi,0x6031f0
0x4019d9 &&&&&& call&& 0x400b40 &__cxa_guard_abort@plt&
0x4019de &&&&&& mov&&& rax,rbx
0x4019e1 &&&&&& mov&&& rdi,rax
0x4019e4 &&&&&& call&& 0x400b70 &_Unwind_Resume@plt&
大家都喜欢使用Singleton模式,用的时候图方便,也喜欢直接在函数里面直接用个静态变量。有的时候也必须使用静态变量,比如需要在程序退出的时候执行析构函数的情况。
但是多线程状态下,VC和gcc不同,不能保证静态变量的线程安全性。VC的这个缺陷导致我们在使用Singleton模式的时候,不能像gcc一样直接采用静态函数成员变量的方式。这就给我们的程序带来了很大的安全隐患和诸多不便。这一点应该引起我们的重视!尤其是在构造函数耗时比较长的时候,很可能给程序带来意想不到的结果。我们必须使用变通的方法,自己来控制类的初始化过程。
以前我在解决这个问题的时候就是直接定义一个全局变量的锁,但是定义全局变量代码不够美观,毕竟不是一个好的风格。同时,加锁解锁也相当影响效率。
下面我给出一个可以作为固定模式使用的范例代码供大家参考,基本思路就是利用函数内部的一个基本类型的变量来控制复杂实例的生成:
class ClassStatic
ClassStatic()
Sleep(1000*10);
m_num = 999;
DWORD WINAPI TestThread( LPVOID lpParam )
static volatile long single = 1;
while(single != 0)
if (1 == _InterlockedCompareExchange(&single, 2, 1))
for ( unsigned int i = 0; i & 1024; i++ )
_mm_pause();
while (single != 0)
static ClassS
single && (single = 0);
printf("Thread[%d] Num[%d]\n", lpParam, test.m_num);
这次的运行结果就正确了:
Thread[3] Num[999]
Thread[2] Num[999]
Thread[1] Num[999]
版权所有 IT知识库 CopyRight (C)
IT知识库 IT610.com , All Rights Reserved.他的最新文章
他的热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)}

我要回帖

更多关于 c 静态变量 线程安全 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信