The memory model is a fascinating topic – it touches on hardware, concurrency, compiler optimizations, and even math.The memory model defines what state a thread may see when it reads a memory location modified by other threads. For example, if one thread updates a regular non-volatile field, it is possible that another thread reading the field will never observe the new value. This program never terminates (in a release build): class Test
{
private bool _loop = true; public static void Main()
{
Test test1 = new Test(); // Set _loop to false on another thread
new Thread(() => { test1._loop = false;}).Start(); // Poll the _loop field until it is set to false
while (test1._loop == true) ; // The loop above will never terminate!
}
}There are two possible ways to get the while loop to terminate: Use a lock to protect all accesses (reads and writes) to the _loop field
Mark the _loop field as volatileThere are two reasons why a read of a non-volatile field may observe a stale value: compiler optimizations and processor optimizations. In concurrent programming, threads can get interleaved in many different ways, resulting in possibly many different outcomes. But as the example with the infinite loop shows, threads do not just get interleaved – they potentially interact in more complex ways, unless you correctly use locks and volatile fields.
Compiler optimizationsThe first reason why a non-volatile read may return a stale value has to do with compiler optimizations. In the infinite loop example, the JIT compiler optimizes the while loop from this: while (test1._loop == true) ;To this:if (test1._loop) { while (true); }This is an entirely reasonable transformation if only one thread accesses the _loop field. But, if another thread changes the value of the field, this optimization can prevent the reading thread from noticing the updated value.
Read more: Igor Ostrovsky Blogging
QR:
{
private bool _loop = true; public static void Main()
{
Test test1 = new Test(); // Set _loop to false on another thread
new Thread(() => { test1._loop = false;}).Start(); // Poll the _loop field until it is set to false
while (test1._loop == true) ; // The loop above will never terminate!
}
}There are two possible ways to get the while loop to terminate: Use a lock to protect all accesses (reads and writes) to the _loop field
Mark the _loop field as volatileThere are two reasons why a read of a non-volatile field may observe a stale value: compiler optimizations and processor optimizations. In concurrent programming, threads can get interleaved in many different ways, resulting in possibly many different outcomes. But as the example with the infinite loop shows, threads do not just get interleaved – they potentially interact in more complex ways, unless you correctly use locks and volatile fields.
Compiler optimizationsThe first reason why a non-volatile read may return a stale value has to do with compiler optimizations. In the infinite loop example, the JIT compiler optimizes the while loop from this: while (test1._loop == true) ;To this:if (test1._loop) { while (true); }This is an entirely reasonable transformation if only one thread accesses the _loop field. But, if another thread changes the value of the field, this optimization can prevent the reading thread from noticing the updated value.
Read more: Igor Ostrovsky Blogging
QR: