由来
在CLR 2.0 Memory Model中,我们知道现代CPU架构从CPU到Memory Controller每一级都有速度,容量 不同的高速缓存。之所以这样设计,主要是因为性能。为了进一步提升性能,当线程读取内存中所期望的 元素值时,CPU并不是只读取我们所期望的元素值,它实际上会同时读取该值周围的若干字节,并将其放 入高速缓存中。这是因为应用程序通常读取的字节在内存中彼此相邻。当应用程序又读取该值周围的字节 时,这些字节已经在高速缓存中了,这样就避免了应用程序再次访问内存,也提升了性能。
应用程序在单核CPU的机器上运行时,高速缓存不会有什么影响。但是当应用程序跑在多CPU/多核CPU 的机器上时,我们就要考虑高速缓存所带来的显著影响了(请参考CLR 2.0 Memory Model)。更槽糕的是, C#或JIT编译器编译代码时,会将指令重新排序。因此,应用程序的执行顺序可能会跟编写的顺序不同, 而且现代CPU本身也支持乱序执行CPU指令。
这样,我们就不得不考虑如何来处理高速缓存一致性。不同的CPU处理方式也不尽相同。比如在CLR 2.0 Memory Model中讲到的x86架构的CPU就会维持高速缓存一致性,而x64架构向后兼容x86架构,所以也 有此特性。但是IA64架构的CPU则被设计用来充分利用每个CPU的高速缓存,而且为了提升性能,尽量避免 高速缓存一致性问题。
为了解决高速缓存一致性所带来的问题,CLR在System.Threading.Thread类中提供了若干个下述形式 的静态方法(这是最简单,最原始的方式,所有的锁机制都会强制高速缓存一致性):
static Object VolatileRead(ref Object address);
static Byte VolatileRead(ref Byte address);
static void VolatileWrite(ref Object address, Object value);
static void VolatileWrite(ref Byte address, Byte value);
static void MemoryBarrier();
所有的VolatileRead方法都执行一个包含获取语义的读取操作,这些方法读取由参数address引用的值 ,然后使得CPU高速缓存内的相应字节失效。所有的VolatileWrite方法则执行一个包含释放语义的写入操 作,这些方法将CPU高速缓存内的字节刷到内存中,然后将address参数引用的值修改为value参数所表示 的值。MemoryBarrier方法则执行一个内存栅栏,将CPU高速缓存内的字节刷到内存中,然后使CPU的高速 缓存内的相应字节失效。
C#编译器提供了volatile关键字,该关键字可以用于下述类型的静态/实例字段:byte,sbyte,short ,ushort,int,uint,char,float和bool。此外,我们还可以将volatile关键字应用于引用类型以及枚 举类型的基础类型是byte,sbyte,short,ushot,int,uint,float和bool的枚举字段。volatile关键 字告诉C#和JIT编译器不再在CPU寄存器中缓存字段,从而确保字段的所有读写操作都是对内存的读写, JIT编译器则确保其语义正确,这样就不必显式调用Thread的静态方法VolatileXXX了。