【问题标题】:How do I atomically read a value in x86 ASM?如何以原子方式读取 x86 ASM 中的值?
【发布时间】:2010-07-28 04:06:18
【问题描述】:

我知道如何在 x86 ASM 中原子地写入一个值。但是我怎么读呢? LOCK 前缀不能与 mov 一起使用。

为了增加价值,我在做:

lock inc dword ptr Counter

如何以线程安全的方式读取 Counter?

【问题讨论】:

    标签: assembly x86 thread-safety atomic


    【解决方案1】:

    正如我在this 帖子中向您解释的那样:

    对可缓存内存的访问 跨越总线宽度、缓存线、 和页面边界不保证 由 Intel Core 2 Duo 实现原子化, 英特尔酷睿双核、奔腾 M、奔腾 4、 Intel Xeon、P6 系列、Pentium 和 Intel486 处理器。英特尔酷睿 2 双核、英特尔酷睿双核、奔腾 M、 Pentium 4、Intel Xeon 和 P6 系列 处理器提供总线控制信号 允许外部存储器子系统 使拆分访问原子化; 然而,非对齐的数据访问将 严重影响性能 处理器,应该避免使用。

    所以使用:

    LOCK        CMPXCHG   EAX, [J]
    

    LOCK CMPXCHG 首先锁定缓存,然后将 EAX 与目标值进行比较,如果目标值不相等,则 EAX 中的结果为目标值。

    编辑: 链接到:

    Intel® 64 and IA-32 Architectures Software Developer’s Manuals

    Volume 3A: System Programming Guide 中查看第 8.1.1 节

    同时检查:Optimization Reference Manual section: CHAPTER 7 OPTIMIZING CACHE USAGE

    【讨论】:

    • 那不会编译,因为 [J] 是一个内存指针。它必须是一个寄存器值。这是我无法绕过的第 22 条规则。
    • 我从您的另一篇文章中看到,只要值对齐并符合 CPU 的总线宽度,这实际上不是问题。
    • @IamIC:确切地说,不是总线宽度。英特尔和 AMD 保证的最低共同点是 mov 加载/存储是原子的 if it doesn't cross an 8-byte boundary (for cached accesses). 或者对于未缓存的,如果它是对齐的或不跨越双字边界的 16 位访问。 [J] 也只是一个绝对或(在 x86-64 中)RIP-relative 寻址模式。这不是双重间接。它组装得很好。 MASM 语法通常会省略 [],但它们在 MASM 中是可选的,在 NASM 中是必需的。
    • 无论如何,因为错误地暗示您需要lock cmpxchg 进行负载而被否决。 在静态数据上,只需在J: 标签前使用ALIGN 4
    • 我在哪里暗示这样的?
    【解决方案2】:

    我不是汇编专家,但字长(在 x86 上,32 位)读/写应该已经是原子的了。

    您需要锁定增量的原因是因为它既是读取又是写入。

    【讨论】:

    • 并非总是如此!如果内存地址在使用多 CPU 单元中的第二个 CPU 的高速缓存中,则不能保证读取是原子的。所以使用 "LOCK CMPXCHG EAX, [var]" 来屏蔽内存缓存。
    • @GJ:我认为这仅适用于未对齐的数据 - 通常您不会有未对齐的数据,所以这应该不是问题?
    • 我知道读取不会是原子的,但它仍然是快照,这意味着值应该是正确的,确定吗?即使您有 2 个 CPU 并且它们的缓存正在同步,我认为 LOCK 不会在确保读取 var 之前的值是最新的方面发挥任何作用......还是会这样?
    • @Paul R:如果两个线程同时在自己的 CPU 下运行并访问某个内存地址,则情况并非如此。在这种情况下,需要缓存同步。一些指令如“LOCK CMPXCHG”会自动执行此操作。像 MOV 这样的指令需要第一个内存栅栏指令来同步缓存内存。检查:英特尔® 64 和 IA-32 架构软件开发人员手册。我已在我的答案中添加了链接。
    • @GJ.: mov 如果地址对齐,则加载/存储是原子的。 x86 有连贯的缓存,所以即使多个 CPU 正在读/写同一个位置,you won't get tearing unless the value is misaligned.
    【解决方案3】:

    对于简单的阅读,主要是关于对齐。确保原子读取的最简单方法是始终使用“自然”对齐 - 即对齐至少与项目的大小一样大(例如,32 位项目是 32 位对齐的)。

    未对齐的读取不一定是原子的。举个极端的例子,考虑在奇数地址读取一个 32 位值,其中第一个字节在一个高速缓存行中,而其他三个字节在另一个高速缓存行中。在这种情况下,原子读取基本上是不可能的。

    由于(至少大多数)处理器使用 64 位宽的内存总线,因此希望以原子方式读取的最大项目是 64 位。

    【讨论】:

      【解决方案4】:

      阅读其他回复很有趣。我认为@GJ 可能正在赚钱。

      多年来,32 位读写是原子的,这一直是事实。直到最近几年,真正激进的缓存才不再保证这一点。

      我想这就是为什么在我和机器代码之间我更喜欢 C++、Java 或其他一些东西的原因。如今,机器代码过于复杂而无法可靠编写(除非您大量这样做以保持您的技能敏锐)。幸运的是,今天的优化编译器非常好,您很少需要手动优化的汇编器的性能。

      【讨论】:

      • C++ 不会保证内存语义高于 CPU 所做的任何事情,没有 volatile 的 Java 也不会。
      • C++ 可以保证对原子类型(C++11 的东西)的原子访问,就像 C (C11) 一样。
      猜你喜欢
      • 2018-06-11
      • 1970-01-01
      • 1970-01-01
      • 2015-08-13
      • 1970-01-01
      • 1970-01-01
      • 2013-04-17
      • 1970-01-01
      相关资源
      最近更新 更多