- Mastering C# Concurrency
- Eugene Agafonov Andrew Koryavchenko
- 641字
- 2021-07-09 21:26:07
Memory model and compiler optimizations
Memory model and compiler optimizations are not directly related to concurrency, but they are very important concepts for anyone who creates concurrent code, shown as follows:
class Program { bool _loop = true; static void Main(string[] args) { var p = new Program(); Task.Run(() => { Thread.Sleep(100); p._loop = false; }); while (p._loop); //while (p._loop) { Console.Write(".");}; Console.WriteLine("Exited the loop"); } }
If you compile this with the Release build configuration and JIT compiler optimizations enabled, the loop will usually hang on the x86 and x64 architectures. This happens because JIT optimizes the p._loop read and does something like this:
if(p._loop) { while(true); }
If there is something inside the while loop, JIT will probably not optimize this code in this way. Also, we may use the volatile keyword with the Boolean flag like this:
volatile bool _loop;
In this case, JIT will turn off this optimization as well. This is where we use a memory model, and it gets complicated here. Here is a quote from the C# language specification:
For non-volatile fields, optimization techniques that reorder instructions can lead to unexpected and unpredictable results in multi-threaded programs that access fields without synchronization such as that provided by the lock-statement. These optimizations can be performed by the compiler, by the run-time system, or by hardware. For volatile fields, such reordering optimizations are restricted:
- A read of a volatile field is called a volatile read. A volatile read has "acquire semantics"; that is, it is guaranteed to occur prior to any references to memory that occur after it in the instruction sequence.
- A write of a volatile field is called a volatile write. A volatile write has "release semantics"; that is, it is guaranteed to happen after any memory references prior to the write instruction in the instruction sequence.
As we can see, there is nothing specifically stated here about compiler optimizations, but in fact JIT does not optimize volatile field read in this case.
So we can see a description in a specification, but how does this really work? Let's look at a volatile read example:
class VolatileRead { int _x; volatile int _y; int _z; void Read() { int x = _x; // 1 int y = _y; // 2 (volatile) int z = _z; // 3 } }
The possible reordering options would be 1, 2, 3 (original); 2, 1, 3; and 2, 3, 1. This can be imagined as a one-way fence that allows the preceding operation to pass through, but does not allow subsequent operations. So this is called the acquire fence.
Volatile writes look pretty similar. Consider the following code snippet:
class VolatileWrite { int _x; volatile int _y; int _z; void Read() { _x = 1; // 1 _y = 2; // 2 (volatile) _z = 3; // 3 } }
Possible options here are 1, 2, 3 (original); 1, 3, 2; and 3, 1, 2. This is the release fence, which allows the reordering of only subsequent read or write operations but does not allow the preceding write operation. We have the Thread.VolatileRead
and Thread.VolatileWrite
methods that do the same thing explicitly. There is the Thread.MemoryBarrier
(memory barrier) method as well, which allows us to use a full fence when we do not let through any operations.
I would like to mention that we are now on less certain ground. Different memory models on different architectures can be confusing, and code without volatile can perfectly work on x86 and amd64. However, if you are using shared data, please be aware of possible reordering and non-reordering optimizations and choose the appropriate behavior.
- Oracle WebLogic Server 12c:First Look
- 樂高機器人設計技巧:EV3結構設計與編程指導
- 算法精粹:經典計算機科學問題的Python實現
- Java EE 7 Development with NetBeans 8
- Python數據可視化之Matplotlib與Pyecharts實戰
- MATLAB for Machine Learning
- 計算機應用基礎案例教程
- Spring Boot+MVC實戰指南
- BeagleBone Robotic Projects(Second Edition)
- 數據結構與算法詳解
- 深度學習的數學:使用Python語言
- Java EE應用開發及實訓
- 瘋狂Ajax講義(第3版)
- C++程序設計習題與實驗指導
- C++ Game Development Cookbook