VC和gcc在保证功能static对线程安全的差异变量
VC和gcc不同,不能保证静态变量的线程安全性。这就给我们的程序带来了非常大的安全隐患和诸多不便。这一点应该引起我们的重视!尤其是在构造函数耗时比較长的时候。非常可能给程序带来意想不到的结果。本文从測试代码開始,逐步分析原理,最后给出解决方式。
多线程状态下。VC不能保证在使用函数的静态变量的时候,它的构造函数已经被运行完成,以下是一段測试代码:
class TestStatic
{
public:
TestStatic()
{
Sleep(1000*10);
m_num = 999;
} public:
int m_num;
}; DWORD WINAPI TestThread( LPVOID lpParam )
{
static TestStatic test;
printf("Thread[%d] Num[%d]\n", lpParam, test.m_num);
return 0;
} 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++)
{
Sleep(1000*10000);
} return 0;
}
測试代码有益在构造函数中制造了一个较长时间的延时。程序执行结果:
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 TestStatic test;
00D48A7D mov eax,dword ptr [$S1 (0D9EA94h)]
00D48A82 and eax,1
00D48A85 jne TestThread+6Ch (0D48AACh)
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);
00D48AAC 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
{
public:
TestStatic()
{
sleep(10);
m_num = 999;
}
public:
int m_num;
}; static void* TestThread( void* lpParam )
{
static TestStatic test;
printf("Thread[%d] Num[%d]\n", lpParam, test.m_num);
return 0;
} int main (int argc, char *argv[])
{
pthread_attr_t ThreadAttr;
pthread_attr_init(&ThreadAttr);
pthread_attr_setdetachstate(&ThreadAttr, PTHREAD_CREATE_DETACHED); pthread_t tid;
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>
0x4019a2 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
{
public:
ClassStatic()
{
Sleep(1000*10);
m_num = 999;
}
public:
int m_num;
}; DWORD WINAPI TestThread( LPVOID lpParam )
{
static volatile long single = 1; while(single != 0)
{
if (1 == _InterlockedCompareExchange(&single, 2, 1))
{
break;
}
else
{
for ( unsigned int i = 0; i < 1024; i++ )
{
_mm_pause();
} while (single != 0)
{
Sleep(1);
}
}
} static ClassStatic test; single && (single = 0); printf("Thread[%d] Num[%d]\n", lpParam, test.m_num); return 0; }
这次的执行结果就正确了:
Thread[3] Num[999]
Thread[2] Num[999]
Thread[1] Num[999]
版权声明:本文博客原创文章,博客,未经同意,不得转载。
VC和gcc在保证功能static对线程安全的差异变量的更多相关文章
- VC和GCC静态变量析构顺序不同
VC和GCC静态变量析构顺序不同(金庆的专栏)静态变量析构顺序正常情况下是构造的反序.但是VC对DLL中的静态变量好像是需等待DLL卸载时再析构,表现为主程序中的静态变量先析构,DLL中的静态变量后析 ...
- extern、static、auto、register 定义变量的不同用法
首先得说明什么叫“编译单元”.每个 .c 文件会被编译为一个 .o 文件,这个就是一个编译单元.最后所有的编译单元被链接起来,就是一个库或一个程序. 一个变量/函数,只要是在全局声明的,链接之后都隐含 ...
- static 和 final 关键字 对实例变量赋初始值的影响
static 和 final 关键字 对实例变量赋初始值的影响 最近一直在看<深入理解Java虚拟机>,在看完了对象内存分配.Class文件格式之后,想深扒一下实例变量是如何被赋上初始值的 ...
- Static和Final修饰类属性变量及初始化
1.static修饰一个属性字段,那么这个属性字段将成为类本身的资源,public修饰为共有的,可以在类的外部通过test.a来访问此属性;在类内部任何地方可以使用.如果被修饰为private私有,那 ...
- [C] zintrin.h : 智能引入intrinsic函数。支持VC、GCC,兼容Windows、Linux、Mac OS X
博客来源:http://blog.csdn.net/zyl910/article/details/8100744 现在很多编译器支持intrinsic函数,这给编写SSE等SIMD代码带来了方便.但是 ...
- VC/MFC ListCtrl 控件功能使用汇总(转)
以下未经说明,listctrl默认view 风格为report 相关类及处理函数 MFC:CListCtrl类 SDK:以 “ListView_”开头的一些宏.如 ListView_InsertCol ...
- GCC高级测试功能扩展——程序性能测试工具gprof、程序覆盖测试工具gcov
gprof是GNU组织下的一个比较有用的性能测试功能: 主要功能: 找出应用程序中消耗CPU时间最多的函数: 产生程序运行时的函数调用关系.调用次数 基本原理: 首先用户要使用gprof工具, ...
- [C/C++] 各种C/C++编译器对UTF-8源码文件的兼容性测试(VC、GCC、BCB)
在不同平台上开发C/C++程序时,为了避免源码文件乱码,得采用UTF-8编码来存储源码文件.但是很多编译器对UTF-8源码文件兼容性不佳,于是我做了一些测试,分析了最佳保存方案. 一.测试程序 为了测 ...
- ftp功能深度剖析 + 线程 031
一 打印进度条 import time for i in range(20): # \r 回到行首打印内容 如果有同一行内容,那么就被抹掉了 n = '>'* i print('\r%s'%n, ...
随机推荐
- NYOJ 47 河问题
时间限制:1000 ms | 内存限制:65535 KB 难度:5 描写叙述 在漆黑的夜里,N位旅行者来到了一座狭窄并且没有护栏的桥边.假设不借助手电筒的话,大家是不管怎样也不敢过桥去的.不幸的是 ...
- POJ 2250 Compromise (UVA 531)
LCS问题.基金会DP. 我很伤心WA非常多.就在LCS问题,需要记录什么路. 反正自己的纪录path错误,最后,就容易上当. 没有优化,二维阵列,递归打印,cin.eof() 来识别 end of ...
- 《实验数据的结构化程序设计》 2.4.4Calendar个人意见,寻求指引
题目大意: 制作一个日历系统,输入年份.一些周年纪念日,及服务要求日期,依据要求日期输出,输出重要程度小于发生日期的周年纪念日. 题目地址: UVA 145 个人见解: 纯模拟,在闰年,输出顺序及输 ...
- python 凸包(经纬度) + 面积[近似]
def cross(A,B): return A[0] * B[1] - A[1] * B[0] def vectorMinus( a , b): return ( (a[0] - b[0] )*10 ...
- Spark的分布式计算
Spark,Spark是什么,如何使用Spark 1.Spark基于什么算法的分布式计算(很简单) 2.Spark与MapReduce不同在什么地方 3.Spark为什么比Hadoop灵活 4.Spa ...
- 国外代理server
这里有几个国外的代理server 另外在网上能够找到很多这种 不能用的时候就在网上搜搜 稳定代理server 有非常多的 IP port 显示地址 24.245.58.130:32167 美国 新泽西 ...
- HDU 3836 Equivalent SetsTarjan+缩点)
Problem Description To prove two sets A and B are equivalent, we can first prove A is a subset of B, ...
- 【Web探索之旅】第四部分:Web程序员
内容简介 1.第四部分第一课:什么是Web程序员? 2.第四部分第二课:如何成为Web程序员? 3.第四部分第三课:成为优秀Web程序员的秘诀 第四部分:Web程序员(完结篇) 大家好.终于来到了[W ...
- 乐在其中设计模式(C#) - 工厂方法模式(Factory Method Pattern)
原文:乐在其中设计模式(C#) - 工厂方法模式(Factory Method Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 工厂方法模式(Factory Method Pa ...
- node.js基础:HTTP服务器
一个HTTP服务器响应 var http = require('http'); http.createServer(function(request,response){ response.end(' ...