Unlike the VC and gcc , there is no guarantee of thread safety for static variables. This has brought a lot of security risks and many inconveniences to our program. This should arouse our attention! Especially when the constructor takes a long time. Very likely to bring unexpected results to the program. This article starts from the test code, analyzes the principle gradually, finally gives the solution way.
Multi-threaded state. VC can not guarantee that when using the static variable of the function, its constructor has been run to complete, the following is a test code:
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;}
The test code is useful for creating a longer time delay in the constructor. Program execution Results:
THREAD[2] num[0]
THREAD[3] num[0]
THREAD[1] num[999]
The results show that thread 2 and thread 3 have already used the variable instance when the constructor for the static variable has not run to completion. And got the wrong result.
Disassembly code from the testthread functions listed below is not difficult to see where the problem lies. When a static variable instance does not exist. The program generates an instance and then calls the constructor. It is two steps to skip the build instance and call the constructor directly when the instance exists.
In combination with the above output, thread 1 calls the function testthreadFirst, so it generates the instance test and starts calling the Teststatic class constructor. The constructor is stuck on sleep . After this, thread 2 and thread 3 successively call the testthread function. However, even though the constructor is not finished, the instance of the static variable already exists, so skipping the build instance and the tune constructor, directly to the call of the printf function, outputs the value of the variable that is not initialized (this is 0). When sleep is complete, the constructor is completed, the value of the variable is set to 999, and only thread 1 Gets the correct result 999.
static teststatic test;
00d48a7d mov eax,dword ptr [$S 1 (0d9ea94h)]
00d48a82 and eax,1
00d48a85 jne testthread+6ch (0d48aach)
00d48a87 mov eax,dword ptr [$S 1 (0d9ea94h)]
00d48a8c or eax,1
00d48a8f mov dword ptr [$S 1 (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)]
......
A similar code. We use gcc to compile the program on Linux to see how it works:
Class Teststatic{public: teststatic () { sleep]; 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);}
Finally the results show. GCC compiled programs and VCs have different results, each thread gets the correct value. It is visible that gcc is a real guarantee of thread safety of static variables inside functions, and the results of the program execution are as follows:
THREAD[3] num[999]
THREAD[2] num[999]
THREAD[1] num[999]
In the same way, we analyze the problem from the disassembly code code of the testthread function.
It is not difficult to see that the biggest difference betweengcc and VC is the call 0x400a50 <[email protected]>, this line of code. GCC acquires the lock before creating the static variable instance, and the constructor runs complete before it finds the instance created successfully. Obviously, this lock is the code that GCC volunteered to join. Therefore, the constructor does not run complete and all threads cannot get to the test variable. Will not be like the VC program output the wrong result.
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
0X4019DE mov rax,rbx
0X4019E1 mov Rdi,rax
0x4019e4 Call 0x400b70 <[email protected]>
Everyone likes to use Singleton mode. It is convenient to use the diagram, and also like to directly use a static variable directly inside the function.
Static variables must also be used at some point. For example, you need to run a destructor when the program exits.
But in the multi-threaded state. VC and gcc are different. There is no guarantee of thread safety for static variables. This flaw of VC causes us to use the Singleton mode, cannot directly adopt the static function member variable way like gcc . This has brought a lot of security risks and many inconveniences to our program. This should arouse our attention! Especially when the constructor takes a long time. Very likely to bring unexpected results to the program.
We have to use a workaround to control the initialization of the class ourselves.
Once I was solving the problem is to directly define a global variable lock, but the definition of global variable code is not beautiful. After all, not a good style.
At the same time, lock unlock also has a considerable impact on efficiency.
Below I give you a sample code that can be used as a fixed mode for your reference. The basic idea is to use a basic type of variable inside a function to control the generation of complex instances:
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 < 1024x768; 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; }
The results of this implementation are correct:
THREAD[3] num[999]
THREAD[2] num[999]
THREAD[1] num[999]
Copyright notice: This article blog original articles, blogs, without consent, may not be reproduced.
VC and GCC differential variables to ensure functional static for thread safety