C# 内存模型是一组规则,描述允许和不允许的内存操作重新排序类型。 所有程序都应该根据在规范中定义的保证进行编写。
C# 内存模型允许在某一方法中对内存操作进行重新排序,只要单线程执行的行为不发生改变即可。
但是,即使允许编译器(JIT)和处理器(CPU)对内存操作进行重新排序,也不意味着它们在实际情况下会始终这样做。
根据这个抽象 C# 内存模型而包含“错误”的许多程序仍会在运行特定版本 .NET Framework 的特定硬件上正确执行。
值得注意的是,x86 和 x64 处理器仅在某些范围较窄的方案中对操作重新排序;
同样,CLR 实时 (JIT) 编译器不会执行所允许的许多转换。
尽管您在编写新代码时应该对这个抽象的 C# 内存模型已心中有数,但理解这个内存模型在不同体系结构上的实际实现方式是很有用的,特别是在尝试理解现有代码的行为时。
主要针对的对象是编译器(JIT)和处理器(CPU)对内存代码重新排序,原子性访问。
备注:原子性代码:如果一组变量总是在相同的锁内进行读写,就可以称为原子的(atomically)读写。假定字段x
与y
总是在对locker
对象的lock
内进行读取与赋值;a++就不是原子性代码
1、内存操作重新排序
2、可变字段 volatile,可以限制内存重排,从内存直接读取数据。
3、原子性:在 C# 中,值不一定以原子方式写入内存。支持原子性数据类型有: reference,bool,char,byte,sbyte,short,ushort,unit,int,float。不知此类型有long\double 因为他们是64位的,所有要看芯片类型,如果64位芯片那么久就支持原子性,32位不支持。
4、不可重新排序优化
主要通过这些模式解决多线程、并行编程和异步编程引发的内存模式所带来的问题。
通过锁定lock(){。。。} ,防止内存操作重新排序和数据原子性问题。
通过线程间的API 进行,防止内存操作重新排序。例如:
class Test2 { static int s_value; static void Run() { s_value = 42; Task t = Task.Factory.StartNew(() => { Console.WriteLine(s_value); }); t.Wait(); } }
通过类型初始化进行发布 将一个值安全地发布到多个线程的另一个方法是将该值写入静态初始值或静态构造函数中的静态字段。 请考虑以下示例:
class Test3 { static int s_value = 42; static object s_obj = new object(); static void PrintValue() { Console.WriteLine(s_value); Console.WriteLine(s_obj == null); } }
如果并行从多个线程调用 Test3.PrintValue,每个线程都确保输出“42”和“false”。
Volatile 关键可以限制内存重拍。更多Volatile 关键字内容
public class DataInit { private int _data = 0; private volatile bool _initialized = false; void Init() { _data = 42; // Write 1 _initialized = true; // Write 2 } void Print() { if (_initialized) { // Read 1 Console.WriteLine(_data); // Read 2 } else { Console.WriteLine("Not initialized"); } } }
但在 _initialized 标记为 _data。因此,Print 将永远不会输出“0”,即使使用 Init 对 DataInit 的新实例进行了并发调用。
lazy initialize
Interlocked.Increment和Thread.MemoryBarrier
联锁操作是原子操作,在许多情况下可用来减少多线程程序中的锁定。 请考虑下面这个简单的线程安全的计数器类:
class Counter { private int _value = 0; private object _lock = new object(); public int Increment() { lock (_lock) { _value++; return _value; } } }
使用 Interlocked.Increment,您可以按照如下所示重新编写该程序:
class Counter { private int _value = 0; public int Increment() { return Interlocked.Increment(ref _value); } }
使用 Interlocked.Increment 程序编写后,该方法应该更快地执行,至少在某些体系结构上会更快。 除了递增操作之外,Interlocked 类 (bit.ly/RksCMF) 还公开以下不同的原子操作的方法: 添加值、有条件地替换值、替换值和返回原始值等。
所有 Interlocked 方法都具有一个非常有趣的属性: 它们不能与其他内存操作互换顺序。 因此,无论是在联锁操作之前还是之后,没有任何内存操作可以通过联锁操作。
与 Interlocked 方法密切相关的一个操作是 Thread.MemoryBarrier,该操作可被视作虚拟联锁操作。 与 Interlocked 方法一样,Thread.MemoryBarrier 不能与任何之前或之后的内存操作互换顺序。 但与 Interlocked 方法不同的是,Thread.MemoryBarrier 没有负面影响;它只是约束内存重新排序。
避免轮询循环。 通常,您可以使用 BlockingCollection<T>、Monitor.Wait/Pulse、事件或异步编程
还未理解
Task \Task<Result>
尽可能使用标准 .NET 并发基元,而不是自己实现等效的功能。
内容来源:https://docs.microsoft.com/zh-cn/archive/msdn-magazine/2012/december/csharp-the-csharp-memory-model-in-theory-and-practice