代码之家  ›  专栏  ›  技术社区  ›  Robert Fraser

.NET内存模型、易失性变量以及测试和设置:什么是有保证的?

  •  10
  • Robert Fraser  · 技术社区  · 14 年前

    我知道.NET内存模型(在.NET框架上,而不是Compact/Micro/Silverlight/Mono/XNA/您拥有什么)保证了某些类型(尤其是原始整数和引用)的操作是原子操作。

    此外,我相信x86/X64测试和设置指令(以及 Interlocked.CompareExchange )实际上引用了全局内存位置,因此如果它成功了另一个 互锁比较交换 会看到新的价值。

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

    这会导致一些问题:

    1. 我的信仰是否正确?
    2. Interlocked.Read 对于int没有重载,只对long有重载(这是2个单词,因此通常不会自动读取)。我总是假设.NET内存模型保证在读取ints/references时会看到最新的值,但是使用处理器缓存、寄存器等,我开始看到这可能是不可能的。那么有没有一种方法可以强制重新获取变量?
    3. volatile是否足以解决整数和引用的上述问题?
    4. 在x86/x64上,我可以假定…

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

    x = 1;
    y = 2;
    

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

    3 回复  |  直到 14 年前
        1
  •  6
  •   wj32    14 年前
    • 只有对宽度不超过32位(X64系统上为64位)的变量的读写才是原子的。这意味着你不会读 利息 得到一半的书面价值。这并不意味着算术是原子的。
    • 互锁操作也充当内存屏障,所以是的, Interlocked.CompareExchange 将看到更新的值。
    • this page . 挥发性并不意味着有序。有些编译器可能选择不重新排序易失变量上的操作,但CPU可以自由地重新排序。如果要阻止CPU重新排序指令,请使用(完全)内存屏障。
    • 内存模型确保读和写是原子的,使用volatile关键字确保读将 总是 来自内存,而不是寄存器。所以你 查看最新值。这是因为x86 CPU将在适当时使缓存失效-请参阅 this this .另请参见 InterlockedCompareExchange64 如何原子地读取64位值。
    • 最后,最后一个问题。答案是一个线程实际上可以看到 x = 0 y = 2 使用volatile关键字不会改变这一点,因为CPU可以自由地重新订购指令。你需要一个记忆障碍。

    总结:

    1. 编译器可以自由地重新排序指令。
    2. CPU可以自由地重新订购指令。
    3. 字大小的读写是原子的。算术和其他操作不是原子操作,因为它们涉及到读取、计算和写入。
    4. 从内存中读取的字大小将始终检索最新值。但大多数时候你不知道你是否真的在从记忆中阅读。
    5. 全存储屏障停止(1)和(2)。大多数编译器允许您自行停止(1)。
    6. volatile关键字确保您正在从内存(4)中读取数据。
    7. 互锁操作(锁前缀)允许多个操作是原子操作。例如,读+写(interlockedexchange)。或读+比较+写(互锁CompareExchange)。它们还充当记忆屏障,因此(1)和(2)被停止。它们总是写入内存(显然),因此(4)是可以保证的。
        2
  •  2
  •   Buu    14 年前

    遇到了这条旧线。汉斯和WJ32的回答都是正确的,除了关于 volatile .

    特别是关于你的问题

    在x86/x64上,我可以假定…如果 有两个全局整型变量 x和y,都初始化为0,如果 我写道: x = 1; y = 2;

    任何线索都看不见 x=0和y=2(即写入将 按顺序发生)。这个换成如果 它们是易挥发的?

    如果 y 是不稳定的,写入 x 保证在写信之前发生 Y 因此,任何线程都看不到 x = 0 y = 2 . 这是因为对易失变量的写入具有“释放语义”(逻辑上相当于释放界限的释放),即在它不会移动之前,所有读/写指令都会通过它。(这意味着,如果x不稳定,而y不稳定,您可能仍然会看到意外 x=0 Y=2 )请参见中的说明和代码示例 C# spec 了解更多详细信息。

        3
  •  0
  •   Hans Passant    14 年前

    不,volatile关键字和原子性保证太弱了。你需要一个记忆屏障来确保这一点。可以使用thread.memorybarrier()方法显式获取一个。