Read the directory
- background
- The code description
- The more analysis, the darker it gets
- conclusion
The background,
This is a bit of a clickbait title, but the story is this: there is a Web API site that has a local Release mode Run problem, but Debug mode does not. Log to locate the problem in the following code:
private static int _flag;
public voidExactlyOnceMethod() {var original = Interlocked.Exchange(ref _flag, 1);
if (original ==_flag) {//1. Repeat
}
else
{
//2. First entry
}}Copy the code
In theory, one request goes into 2, but the real problem is that all go into 1.
Ii. Code description
This code is very simple. It does two things. One is to assign the _flag variable to interlocked. Exchange. 2 compares the original value returned by interlocked. Exchange with the _flag variable. If the value is equal, the variable has been modified, indicating reentrant. If not, the method is entered for the first time.
For an explanation of Interlocked.Exchange, see the documentation on Microsoft’s official website, portal here: msdn.microsoft.com/zh-cn/libra…
Three, the more analysis, the darker
Ok, I looked at it for a few minutes, but I didn’t see anything wrong, so let’s first consider the multithreading problem. But the only shared variable here is _flag, which is again the CAS operation, and there is no multi-threading problem here. And combined with logging, it is true that this method is executed only once. Take a closer look at the official document again, as shown in Figure 1 below. I noticed that the sample code was written differently from the code I posted above. Instead of reusing the variable usingResource, the comparison object was changed to a constant 0.
“Figure 1”
Intrigued, I checked it out. Net Framework source code. Portal referencesource.microsoft.com/#mscorlib/s here… . But it’s directly an extern method, as shown in Figure 2 below:
Figure 2 】 【
There’s another dilemma here, and now the trail is dead. Access to some data, MethodImplOptions InternalCall indicates that the implementation of this method can be found in Microsoft’s open source sscli bbs.csdn.net/topics/3300 answer (the original address… In the 5th floor reply). But after all parties search, has not found the source location, is said to be. Net Framework 2.0 era.
OK, I’d like to take a look at the assembly code. Here is the decompiled assembly code:
1 var original = Interlocked.Exchange(ref _flag, 1);
2 00DC35EF movEcx,5F2DFCCh // Put the data on the address 5F2DFCCh into the register ECx 3 00DC35F4 mov edx,1// Place 1 in register edx 4 00DC35F9 call 70B95330// Call the method at address 70B95330 5 00DC35FE movDword PTR [ebP-48h], eAX // Save register eAX to address EBP-48h double font pointer 6 00DC3601 movEax,dword PTR [ebP-48h] // Add data to register ebP-48h 7 00DC3604 movDword PTR [ebP-40h], eAX // Save the data in register EAX to address EBP-40h double font pointer 8 if (original == _flag)
9 00DC3607 movEax,dword PTR [ebP-40h] eAX,dword PTR [EBP-40h] eAX,dword PTR [EBP-40h10 00DC360A cmp eax,dword ptr ds:[5F2DFCCh] //Compare address ds:Data on the [5F2DFCCh] double font pointer and data in register EAX. From here, the following code is not our discussion point, so I won't translate it11 00DC3610 setne al
12 00DC3613 movzx eax,al
13 00DC3616 mov dword ptr [ebp-44h],eax
14 00DC3619 cmp dword ptr [ebp-44h],0
15 00DC361D jne 00DC3624 Copy the code
So this 5F2DFCCh is actually _flag. We can see that in the actual interlocked. Exchange operation, we did not directly modify the 5F2DFCCh address, but in the CMP operation, we continued to use the 5F2DFCCh address because of the _flag variable. That is, the CPU operates on the data in the register, but the variable we use to determine is a static global variable that holds the reference address. If Interlocked’s internal operations do not use the same CPU core as the current context, the criteria are not as written in the code, because the variables are expected to be the same. The reason is that while Interlocked is in CPU1’s cache, the data loaded by another operation on CPU2 is still in memory. There is a very short time lag for CPU1 to synchronize the data into memory (assigning the value in the register to the global variable _flag). If this is the case, it could explain three things:
1. It works on some machines and it doesn’t work on some machines.
2. There is no problem in Debug mode, but there is a problem in Release mode.
3. It is also ok to add a log to the physical file before the if statement.
According to this theory, the reason is that the time difference is related to the hardware configuration environment of the machine. As long as the “assign” operation takes less time than the code takes to execute to if, there should be no problem. The solution is to make this expression work, even if it’s a Sleep1 millisecond. The author suggests two solutions:
Solution 1: Add volatile to the global variable (docs.microsoft.com/zh-cn/dotne…). .
Option 2: Refer to the official example writing method, replace _flag with a constant for comparison, for example, here can be changed to original == 0.
Four, conclusion
To sum up:
CAS done with Interlocked is itself a CPU operation. Data is swapped in CPU registers. But the variable we determine is a static global variable that holds this reference address.
The process in question is:
1. Load data from the incoming ref reference address into the CPU register
2. The register is swapped and the original value is returned, but updating the reference address is not a synchronous operation in this context.
3. Then when we compare, the original value on the left side is definitely 0, but the variables in flow 1 are also original 0 for a very short time (see Figure 3). Which leads to this problem.
[3]
Again, this is based on the assumption that Interlocked’s internal operations are not using the same CPU core as the current context. This information is not available and cannot be verified, so I am not 100% sure. If anyone can give a clear answer, please leave a comment below
In the process of analyzing this problem, I refer to the thought results of the following partners, thank you for sharing:
286. iteye.com/blog/229516…
www.cnblogs.com/5iedu/p/471…
Blog.csdn.net/hsuxu/artic…
By Zachary_Fan www.cnblogs.com/Zachary-Fan…
If you want to be notified of your own articles, please scan the qr code below.