Why does the getter Val
happen to simulate volatility for the field val
?
I'm assuming that leveraging a method call is not a reliable way to keep the variable volatile.
(To try it out, build for release and execute directly without the debugger.)
class Program
{
private int val = 0;
public int Val { get { return val; } }
public static void Main()
{
var example = new Program();
Task.Run(() => example.val++);
while (example.val == 0) ; // Hangs if val is not volatile
while (example.Val == 0) ; // Never seems to hang
}
}
Alright, it turns out that the jitter is allowed to assume that all non-volatile variables are only accessed by one thread (much like in the C++11 memory model where concurrent accesses to non-std::atomic<>
variables invoke undefined behaviour). In this case, the jitter is optimizing that first loop into loop: test eax, eax; je loop
(it hoists the variable access into a register that is never updated), so obviously it never terminates.
The second loop generates assembly that reads the value relative to the object's pointer, so eventually it sees the new value (though possibly out of order with respect to other writes on the other thread, again because the variable is not volatile). This is coincidental due to the assembly that happened to be generated.
x86 assembly generated for first (infinite) loop:
003B23BA test eax,eax
003B23BC je 003B23BA
x86 assembly for second (finite) loop:
002F2607 cmp dword ptr [eax+4],0
002F260B je 002F2607
Since the jitter is allowed to assume non-volatile variables are never touched by other threads, you can only rely on volatile
to work as expected (even if it appears to in a given circumstance, like this one, because future optimisations (or different CPU architectures, etc.) will probably break your code in difficult-to-debug ways).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With