Interlocked, an atomic operation in C #, do you really understand it ?,
Reading directory
- Background
- Code Description
- The darker the analysis
- Conclusion
I. background
This title starts with a bit of suspicion from the title party [cover your face]. The original principle of this case is that there is a problem when a Web API site uses the Release mode Run locally, but not in Debug mode. Locate the problem by logging in the following code:
Private static int _ flag; public void ExactlyOnceMethod () {var original = Interlocked. exchange (ref _ flag, 1); if (original = _ flag) {// 1. repeat} else {// 2. first entry }}
In theory, there will be one request to enter 2, but the actual problem is that all requests enter 1.
Ii. Code Description
This code is very simple. There are two things to do. 1 is to use Interlocked. Exchange to assign values to the _ flag variable. 2. Compare the original value returned after the Interlocked. Exchange Operation with the _ flag variable. If the value is equal, it indicates that the variable has been modified. This indicates that it is re-imported. If not, it indicates the first time you enter this method.
For an explanation of Interlocked. Exchange, see the documentation on the Microsoft website. The transfer is here: https://msdn.microsoft.com/zh-cn/library/d3fxt78a.aspx
3. the darker the analysis
Well, after reading it for several minutes, I didn't see anything wrong, so I should first consider multithreading. However, the only shared variable here is the _ flag, and the CAS operation is used. There is no multithreading problem here. Combined with log output, this method is indeed only executed once. Take a closer look at the content in the official document. See figure 1. I found that the writing method in the sample code is different from the code I posted above. The usingResource variable is not reused here, and the comparison object is directly changed to a constant 0.
Figure 1]
With curiosity, I went through the source code of. Net Framework. The portal is in this http://referencesource.microsoft.com/#mscorlib/system/threading/interlocked.cs,52be0cc9b3954ae9. But it is an extern method, see 2:
Figure 2]
This is another dilemma, and now the clues are broken. After reading some information, MethodImplOptions. InternalCall indicates that the implementation of this method can find the answer in the Microsoft Open Source sscli (the original address in the http://bbs.csdn.net/topics/330019064 of the 5 floor reply ). However, the source code cannot be found by all parties, which is said to be a product of the. Net Framework 2.0 era.
OK, so I want to try the assembly code. The following is the decompiled assembly code:
1 var original = Interlocked. exchange (ref _ flag, 1); 2 00DC35EF mov ecx, 5F2DFCCh // put the data on the 5F2DFCCh address into the register ecx 3 00DC35F4 edx mov, 1 // put 1 into the register edx 4 00DC35F9 call 70B95330 // call Method on 70B95330 address 5 00DC35FE mov dword ptr [ebp-48h], eax // Save the data of register eax to the dual-font pointer of address ebp-48h 6 00DC3601 mov eax, dword ptr [ebp-48h] // put the data on the dual-font pointer of the address ebp-48h into the register eax (which can be understood as the reverse operation of the previous step) 7 00DC3604 mov dword ptr [ebp-40h], eax // Save the eax data to the address eb On the double-font pointer of the p-40h 8 if (original ==_ flag) 9 00DC3607 mov eax, dword ptr [ebp-40h] // put the data on the dual-font pointer of the address ebp-40h into the register eax10 00DC360A cmp eax, dword ptr ds: [5F2DFCCh] // compare address ds: [5F2DFCCh] data on the dual-font pointer and in the register eax. Here the following code is not our discussion point, will not translate 11 00DC3610 setne al12 00DC3613 movzx eax, al 13 00DC3616 mov dword ptr [ebp-44h], eax 14 00DC3619 cmp dword ptr [ebp-44h], 0 15 00DC361D jne 00DC3624
The 5F2DFCCh here is actually _ flag. We can see that Interlocked is actually implemented. during the Exchange operation, the data on the 5F2DFCCh address is not directly modified, but during the cmp operation, because the object we compared is the _ flag variable, therefore, we continue to use the data on the 5F2DFCCh address. That is to say:CPU operations operate data in registers, but the variable we use to judge is a static global variable.. Can this be understood as follows :【If Interlocked's internal operations are not the same CPU core as the current context, this "judgment basis" is not as written in code, because we expect the same (all variables are the same). The reason is that when Interlocked is performed, the data loaded by another operation on CPU2 is in the cache of cpu1. CPU1 synchronizes data to the memory (assign the value in the register to the global variable _ flag) with a very short time difference. In this case, we can explain why the following three situations occur:
1. There are no problems on some machines, and there are problems on some machines.
2. There is no problem in Debug mode and there is a problem in Release mode.
3. It is okay to add a log before the if statement to the physical file.
Based on this assumption, the reason is that the time difference is related to the hardware configuration environment of the machine. As long as the time used for this "assign value" Operation <the time required for code execution to if, no problem occurs. According to this conclusion, the solution can be drawn, that is, let this expression be established, even if it is a simple and crude Sleep1 Ms. I suggest two solutions:
Solution 1: to add the volatile keyword to this global variable, please refer to the description of the keyword here (https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/volatile ).
Solution 2: replace _ flag with a constant in the official example for comparison. For example, you can change it to original = 0.
Iv. Conclusion
Summary:
CAS using Interlocked itself is a CPU operation. Data is exchanged in the CPU register. However, the variable we determine is a static global variable, holding the reference address.
That is, the process of the problem is:
1. load data from the input ref reference address to the CPU register
2. The register performs swap and returns the original value. However, the update operation of the referenced address is not a synchronization operation in this context.
3. During the comparison, the original value on the left must be 0, but the variable in process 1 is also the original value 0 (3) in a very short period of time ). This problem occurs.
Figure 3]
This conclusion is also based on[If Interlocked's internal operations are not the same CPU core as the current context]I guess this information is not found and cannot be further verified, so I am not sure whether it is correct. If any of you can give me a clear explanation, please leave a message below ~
In the process of analyzing this problem, I have referred to the ideas and achievements of the following friends. Thank you for sharing them:
Http://286.iteye.com/blog/2295165
Http://www.cnblogs.com/5iedu/p/4719625.html
Http://blog.csdn.net/hsuxu/article/details/9467651
Author: Zachary_Fan
If you want to get a message pushed from your own article in time, scan the following QR code ~.