.NET内存模型,volatile变量和测试集:保证什么?

Rob*_*ser 10 .net synchronization volatile test-and-set memory-fences

我知道.NET内存模型(在.NET Framework上;不是compact/micro/silverlight/mono/xna/what-have-you)保证对于某些类型(最值得注意的是原始整数和引用)操作保证是原子.

此外,我相信x86/x64测试和设置指令(和Interlocked.CompareExchange)实际上引用了全局内存位置,因此如果成功,另一个Interlocked.CompareExchange将看到新值.

最后,我相信volatile关键字是指令编译器尽快传播读写操作并不重新排序有关此变量的操作(对吗?).

这导致了一些问题:

  1. 我的信念是否正确?
  2. Interlocked.Read没有int的重载,只有longs(2个WORD,因此通常不会原子读取).我一直认为.NET内存模型保证在读取整数/引用时会看到最新的值,但是使用处理器缓存,寄存器等等.我开始意识到这可能是不可能的.那么有没有办法强制重新获取变量?
  3. volatile是否足以解决整数和引用的上述问题?
  4. 在x86/x64上,我可以假设......

如果有两个全局整数变量x和y,则初始化为0,如果我写:

x = 1;
y = 2;
Run Code Online (Sandbox Code Playgroud)

那个NO线程将看到x = 0和y = 2(即写入将按顺序发生).如果它们不稳定,这会改变吗?

wj3*_*j32 6

  • 只读取和写入最多32位宽(在x64系统上为64位宽)的变量是原子的.所有这些意味着你不会读取int并获得半值.这并不意味着算术是原子的.
  • 互锁操作也充当了内存屏障,所以是的,Interlocked.CompareExchange会看到更新后的值.
  • 看到这个页面.挥发性并不意味着有序.有些编译器可能会选择不对volatile变量重新排序操作,但CPU可以自由重新排序.如果要阻止CPU重新排序指令,请使用(完整)内存屏障.
  • 内存模型确保读取和写入是原子的,并且使用volatile关键字确保读取始终来自内存,而不是来自寄存器.所以你看到最新的价值.这是因为x86处理器将缓存失效时适当的-看到这个这个.另请参阅InterlockedCompareExchange64,了解如何以原子方式读取64位值.
  • 最后,最后一个问题.答案是一个线程实际上可以看到x = 0y = 2,并且使用volatile关键字不会改变它,因为CPU可以自由重新排序指令.你需要一个记忆障碍.

摘要:

  1. 编译器可以自由重新排序指令.
  2. CPU可以自由重新排序指令.
  3. 字大小的读写是原子的.算术和其他操作不是原子操作,因为它们涉及读取,计算,然后写入.
  4. 内存中的字大小读取将始终检索最新值.但大多数时候你不知道你是否真的从记忆中读书.
  5. 完整的内存屏障停止(1)和(2).大多数编译器允许您自行停止(1).
  6. volatile关键字确保您从内存中读取 - (4).
  7. 互锁操作(锁定前缀)允许多个操作是原子操作.例如,读取+写入(InterlockedExchange).或者读取+比较+写入(InterlockedCompareExchange).它们也充当记忆障碍,因此(1)和(2)停止.它们总是写入内存(显然),因此确保(4).

  • 在关于.NET的链接到"C关键字神话消除"的问题是关于"volatile"的神话的一个主要来源是人们在C,C#和Java中表现得相同.在C#`volatile`确实有一些排序语义,根据http://msdn.microsoft.com/en-us/library/aa645755%28v=VS.71%29.aspx. (2认同)