【问题标题】:Can CAS fail for all threads?CAS 能否对所有线程都失败?
【发布时间】:2018-05-10 08:47:03
【问题描述】:

我正在阅读[lock cmpxchg描述])https://www.felixcloutier.com/x86/CMPXCHG.html):

该指令可以与LOCK 前缀一起使用以允许 要以原子方式执行的指令。为了简化 到处理器总线的接口,目标操作数 接收一个写周期而不考虑的结果 比较。如果目标操作数被写回 比较失败;否则,源操作数是 写入目的地。 (处理器从不 产生一个锁定的读取,而不产生一个锁定的 写。)

现在考虑两个线程执行lock cmpxchg

Thread 1                Thread 2
mov ebx, 0x4000                mov ebx, 0x4000 ; address
mov edx, 0x62ab6                mov edx, 0x62ab8 ;  new val
mov eax, 0x62ab1                mov eax, 0x62ab1 ;  old
lock cmpxchg [ebx], eax                lock cmpxchg [ebx], eax ;  <----- here

问题是线程 1 和线程 2 中的锁定 cmpxchg 会失败吗?

自从

目标操作数 接收一个写周期而不考虑的结果 比较

我可以猜测这两个线程都可以具有写入周期,并且由于与陈旧值进行比较,它们都可以恢复......但我不确定这是否正确。

也许我需要看一下cas实现细节,但是intel指令参考中没有指定(至少我找不到)

【问题讨论】:

  • 问题出在“旧值”上。加载 eax 后,内存中的值可能会在开始比较之前再次更改。
  • @BoPersson 你能扩展一下吗?又被谁改了?好的,假设两个线程都将旧值加载到不同内核上的eax 中。内核开始执行lock cmpxchg之后接下来会发生什么。
  • “又被谁更改了?” - 由另一个线程或任何人(可能有更多的内核摆弄内存)。 lock 将确保其中一个核心“获胜”并首先获得对该指令的访问权限。另一个必须等​​待(然后会看到修改后的值)。
  • @BoPersson 好的,明白了,谢谢。因此,使用lock 前缀无论如何都会有一个线程获胜。我对 LL/SC pair 可能会虚假失败并且没有文档说 cas 不能这样的事实感到困惑。顺便说一句,你能推荐一些关于英特尔 CPU 上的CAS 实现细节的阅读吗?
  • 您的示例实际上并没有说您知道旧值是0x62ab1。也许这样说,或者更好地将其更改为 mov eax, [ebx] 以实际加载旧值。并且还断言没有第三个线程可以改变它。

标签: assembly concurrency x86-64 compare-and-swap


【解决方案1】:

我的理解是 lock cmpxchg 不能虚假失败 - 不像 LL/SC - 假设内存地址的值确实匹配。它通过缓存行的独占所有权从缓存一致性协议构建这些保证,并且在操作完成之前不将其让给其他内核。

因此,只有当其他线程写入内存位置时,所有线程的 CAS 才会失败。

【讨论】:

    【解决方案2】:

    @the8472 的答案是正确的,但我想添加一个替代答案。

    https://www.felixcloutier.com/x86/CMPXCHG.html 已经足够详细地指定了行为以排除虚假失败的可能性。如果它可能由于内存中的值与eax 不匹配以外的其他原因而失败,则文档必须这样说。

    您还可以注意到编译器对C++11 std::atomic::compare_exchange_strong 使用单个lock cmpxchg 的事实,从中可以得出结论,编译器编写者认为lock cmpxchg 不会虚假失败。

    #include <atomic>
    
    bool cas_bool(std::atomic_int *a, int expect, int want) {
        return a->compare_exchange_strong(expect, want);
    }
    

    compiles to (gcc7.3 -O3):

    cas_bool(std::atomic<int>*, int, int):
        mov     eax, esi
        lock cmpxchg    DWORD PTR [rdi], edx
        sete    al
        ret
    

    另请参阅Can num++ be atomic for 'int num'?,了解有关locked 指令如何在内部实现以及它们如何与MESI 交互的更多详细信息。 (即 @the8472 的答案是简短版本:对于不跨越缓存行的操作数,核心只是挂在该缓存行上,因此系统中的其他任何内容都无法在 @ 期间读取或写入它987654334@)。


    目标操作数接收一个写周期,而不考虑比较结果

    相对于系统中的所有其他观察者而言,读 + 写对是原子的。您提出的 read1 / read2 / write1 / abort write2 的顺序是不可能的,因为lock cmpxchg 是原子的,所以read2 不能按全局顺序出现在 read1 和 write1 之间。

    此外,该语言仅适用于外部存储器总线。具有集成内存控制器的现代 CPU 可以做任何他们想做的事情(对于lock cmpxchg 的地址,该地址被分成两个缓存线)。英特尔可能会发布文档供主板供应商在内存总线上的信号内部测试中使用。

    该文档可能仍然与 MMIO 地址上的 lock cmpxchg 相关,但绝对不适用于回写内存中的对齐操作数。在这种情况下,它只是一个缓存锁。(当比较失败时,是否写入 L1d 缓存是一个隐藏的实现细节)。我想您可以通过查看它是否弄脏缓存行来测试它(即,将其置于修改状态而不是独占状态)。

    有关lock cmpxchgxchg 在内部如何工作的更多讨论,请在我对Exit critical region 的回答之后查看我和@BeeOnRope 之间的聊天线程。 (主要是我的想法在理论上可行,但与我们对 Intel x86 CPU 的了解不兼容,@BeeOnRope 指出了我的错误。https://chat.stackoverflow.com/transcript/message/42472667#42472667。关于效率的细节,我们几乎无法确定xchg vs. lock cmpxchgxchg 保持高速缓存行锁定的周期少于lock cmpxchg,当然有可能,但这需要进行测试。我认为xchg 具有更好的延迟,如果使用 back-to-不过,从单个线程返回到同一位置。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-17
      • 2014-11-03
      • 2013-03-01
      • 2023-01-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多