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对线程安全的差异变量相关推荐
- [C] 跨平台使用Intrinsic函数范例1——使用SSE、AVX指令集 处理 单精度浮点数组求和(支持vc、gcc,兼容Windows、Linux、Mac)...
作者:zyl910. 本文面对对SSE等SIMD指令集有一定基础的读者,以单精度浮点数组求和为例演示了如何跨平台使用SSE.AVX指令集.因使用了stdint.zintrin.ccpuid这三个模块, ...
- c语言vc数据类型长度,vc和gcc对C语言数据类型长度的定义
vc2010中...... 测试代码: #include #include int main() { printf("in windows ...\n"); int l_char= ...
- 从VC++到GCC移植:谈两者的语法差异
从VC++到GCC移植:谈两者的语法差异 许式伟 (版权声明) 2007-1-28 类型引用 template <class T> class Foo { typedef T:: ...
- 关于vc++6.0“打开文件“功能问题的解决方法
关于vc++6.0"打开文件"功能问题的解决方法 一.先下载FileTool.exe 二.安装外接程序 1. 运行 FileTool.Exe ,实际上是解压一个filetoo ...
- 用VC实现GIS系统基本功能
用VC实现GIS系统基本功能 文档说明: 此文档适合VC++的初学者,高手也可参考(希望能提出宝贵意见). 开发前准备: 前提:必须在你的电脑上安装了MapX控件,并且准备好需要用到的电子地图(Map ...
- controller如何保证当前只有一个线程执行_今天我们来聊一聊 Spring 中的线程安全性...
优质文章,及时送达 Spring与线程安全 Spring作为一个IOC/DI容器,帮助我们管理了许许多多的"bean".但其实,Spring并没有保证这些对象的线程安全,需要由开发 ...
- JAVA 编写程序实现如下功能:一个线程进行如下运算1*2+2*3+3*4+……+19*20,而另一个线程则每隔一段时间读取前一个线程的运算结果。
编写程序实现如下功能:一个线程进行如下运算12+23+34+--+1920,而另一个线程则每隔一段时间读取前一个线程的运算结果. class Count{private int sum;Count(i ...
- controller如何保证当前只有一个线程执行_聊聊Spring线程安全,读完之后,大部分程序员收藏了......
Spring作为一个IOC/DI容器,帮助我们管理了许许多多的"bean".但其实,Spring并没有保证这些对象的线程安全,需要由开发者自己编写解决线程安全问题的代码. Spri ...
- eclipse代码自动提示设置、如何配置eclipse的代码自动提示功能(同时解决自动补全变量名的问题)?
eclipse代码自动提示设置.如何配置eclipse的代码自动提示功能(同时解决自动补全变量名的问题)? 参考文章: (1)eclipse代码自动提示设置.如何配置eclipse的代码自动提示功能( ...
- VC++中多线程学习(MFC多线程)三(线程同步包含:原子互锁、关键代码段、互斥器Mutex、Semaphores(信号量)、Event Objects(事件))
目录 线程同步的必要性: 2.解决同步问题的方法 2.1原子互锁家族函数 2.2Critical Sections(关键代码段.关键区域.临界区域) 2.3 互斥器Mutex ...
最新文章
- Linux 环境变量配置
- 如何用视觉的方法量身高?
- 谷歌医疗AI再遭重挫!自宫头牌App,副总裁也已离职
- [云炬创业基础笔记]第二章创业者测试5
- 利用日志传送实现高可用性
- python编写系统随机产生一个数、玩家最多可以猜五次_南开20秋学期《Python编程基础》在线作业(题目随机)...
- linux sed 空间模式,整理:SED的模式空间与缓冲区及n,N,d,D,p,P,h,H,g,G,x解析...
- P4345-[SHOI2015]超能粒子炮·改【Lucas定理,类欧】
- apache.camel_Apache Camel 2.14中的更多指标
- .Net 中的反射(反射特性) - Part.3 - (转载)
- Vhost dataplane in Qemu | PDF
- 学习node js 之微信公众帐号接口开发 准备工作
- 联想服务器虚拟化解决方案,联想虚拟化解决方案
- apache的es的原理_Elasticsearch Lucene 数据写入原理 | ES 核心篇
- tomcat jquery mysql_Docker 搭建 Tomcat + Mysql
- 推荐一款绘图神器visual-paradigm
- 经常看直播,你知道如何用jmeter对直播间进行压力测试吗?
- S参数模型和 IBIS模型入门
- 苹果发布iOS 12.5.1,以修复旧设备上的COVID-19暴露通知
- mysql using temporary_执行计划中Using filesort,Using temporary相关语句的优化解决_MySQL
热门文章
- SQLServer 分组查询相邻两条记录的时间差
- 无法嵌入互操作类型“Microsoft.Office.Interop.Excel.ApplicationClass”。请改用适用的接口 ....
- idea中ssm自动配置
- 03-字典的增删改查
- stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四种智能指针使用总结
- Spark Streaming实例
- ubuntu的无线网无法连上
- Mysql Fabric实现学习笔记
- js-事件处理(重点)
- 如何在Android应用里对HTTP请求头部添加适当的User-Agent字段