文档目录

这个问题切中了移动端和服务器端底层设计的根本分歧。很多从x86转到ARM的开发者,第一反应就是:“为什么ARM没有lock cmpxchg?"。

答案很哲学:x86是复杂(CISC)的"霸道总裁”,而ARM是精简(RISC)的"优雅绅士"。前者直接给你原子指令,后者给你两条指令让你自己组合,以此换取极致的灵活性。


1. 为什么手机芯片(ARM)不直接用 CAS?

直接原因:CAS(Compare-And-Swap)是一个"读-改-写"三合一的巨无霸指令。 在RISC哲学里,一条指令只做一件事。如果强行在ARM里塞入cmpxchg,会:

  • 增加晶体管开销:需要复杂的微码控制器。
  • 破坏多核扩展性:未来的ARM可能几百个核,硬件的CAS逻辑会变成瓶颈。
  • 功耗太高:手机对功耗极其敏感,cmpxchg需要持续锁住总线等待结果,而ARM的LL/SC允许在失败时提前释放。

根本区别在于:

  • x86的CAS:你告诉CPU “你要么成功,要么失败”,CPU内部硬扛所有冲突。
  • ARM的LL/SC:CPU告诉你 “我给你做个标记,你试着改,如果我发现有人动过,我就让你重试”——把"判断冲突"的主动权交给软件。

2. 核心兵器拆解:LL(Load-Link)与 SC(Store-Conditional)

这是ARM体系(以及RISC-V、PowerPC)的原子操作基石。

指令原型(ARMv8 示例)

; 假设我们要实现原子自增: *ptr += 1

1.  LL  x0, [x1]      ; Load-Link: 将 x1 地址的值读入 x0,并打上"独占标记"
2.  ADD x0, x0, #1    ; 普通加法(此时还没写回)
3.  SC  x0, [x1]      ; Store-Conditional: 尝试把 x0 写入 x1
                       ; 如果成功,x0=0;如果失败(独占标记丢失),x0=1
4.  CBNZ x0, retry    ; 如果 x0 != 0,跳回重试

硬件执行流程(独占监视器机制)

[ 核心A ]                           [ 核心B ]                    [ 全局监视器 ]
    |                                    |                              |
    |--- 1. LL (读取地址P) -------------->| ---------------------------> | 记录: 核心A 正在监视 地址P
    |   (核心A: 拿到数据10)              |                              | (Exclusive Monitor 置位)
    |                                    |                              |
    |   (此时核心A做加法,但没写)          |--- 对同一地址P 执行写操作 --> | 检测到冲突!
    |                                    | (核心B: 写入11)             | 标记: 核心A 的监视失效
    |                                    |                              |
    |--- 3. SC (尝试写回11) ------------->| ---------------------------> | 检查: 核心A 的监视还在吗?
    |   (返回失败: x0=1)                 |                              | 回复: 不在 (已失效)
    |                                    |                              |
    |--- 4. 检测失败 -> 跳转重试        |                              |

3. 硬件独占监视器(Exclusive Monitor)的工作原理

这是LL/SC的灵魂。它不是锁,而是一个监听状态的触发器。分为两级:

本地监视器(Local Monitor)

  • 位于每个CPU核心内部。
  • 执行 LL 时,记录下地址标签(通常是地址的哈希,而非完整地址)。
  • 执行 SC 时,检查本地是否还有这个标签。

全局监视器(Global Monitor / Snoop Unit)

  • 位于**总线互联(Interconnect)**上。
  • 当其他核心执行写操作时,总线会广播 “Invalidate” 消息。
  • 全局监视器收到消息后,会清空对应核心的本地监视器标记。

关键特性(面试陷阱):

监视器不阻塞总线!它只是"贴标签"。其他核心写数据时,不需要等待监视器解锁,直接覆盖,只是在覆盖的同时撕掉你的标签。


4. LL/SC 的致命弱点(也是它的特点)

问题 原因 应对策略
伪失败(Spurious Failure) 监视器可能因为上下文切换、缓存驱逐、中断处理而提前失效,即使没人改数据,SC 也会失败。 重试循环(所以ARM的CAS比x86稍慢)
活锁(Live Lock) 多线程同时LL/SC,互相撕标签,导致谁都成功不了。 引入指数退避(Exponential Backoff) 或 随机延迟
指令数量多 至少3条指令(LL, 计算, SC, 判断)。 ARMv8.1 引入了原子指令扩展(LSE),硬件实现了原子CAS(后面讲)

C++ 代码映射(ARM底层无 lock 前缀,而是生成 LL/SC 循环)

#include <atomic>

std::atomic<int> counter{0};

void arm_increment() {
    int expected, desired;
    do {
        // 底层编译为:
        // 1. LDXR (Load Exclusive, 即 LL)
        // 2. ADD
        // 3. STXR (Store Exclusive, 即 SC)
        expected = counter.load(std::memory_order_relaxed);
        desired = expected + 1;
    } while (!counter.compare_exchange_weak(expected, desired, 
             std::memory_order_release, std::memory_order_relaxed));
}

反汇编(ARM64):

.Lretry:
    LDXR    w0, [x1]        ; LL: 加载独占
    ADD     w0, w0, #1
    STXR    w2, w0, [x1]    ; SC: 条件存储,w2 返回 0(成功) 或 1(失败)
    CBNZ    w2, .Lretry     ; 如果失败,重试

5. 重大升级:ARMv8.1 的 LSE(Large System Extensions)

手机芯片越来越强(如苹果M系列、骁龙8系),软件开发者抱怨LL/SC太慢。ARM在v8.1版本直接硬加入了原生原子指令,抹平了与x86的差距。

新增指令(硬件实现CAS)

; 直接在硬件完成 Compare-And-Swap
CAS  w0, w1, [x2]   ; 比较 w0 和 [x2],如果相等,将 w1 写入 [x2]
; 这条指令和 x86 的 lock cmpxchg 行为完全一致!

C++ 行为变化:

  • 如果你的手机CPU支持ARMv8.1(现代旗舰都支持),std::atomic::compare_exchange 不再编译为LL/SC循环,而是直接编译为单条 CAS 指令。
  • 性能提升 30%~50%,且功耗更低。

6. 终极对比表:x86 CAS vs ARM LL/SC

维度 x86 (lock cmpxchg) ARMv8.0 (LL/SC) ARMv8.1+ (LSE CAS)
指令数量 1条(微码实现) 3~4条(循环) 1条(硬连线)
总线占用 直到完成(可能阻塞) 仅写回时检查(不阻塞) 同x86
失败处理 硬件自动重试内部微码 软件显式重试(暴露给C++) 硬件自动重试
功耗 较高 低(失败无需回滚) 中等
ABA问题 存在 存在(但监视器会部分缓解) 存在
C++语义 compare_exchange_strong compare_exchange_weak(推荐) 同x86

7. 给你的"高性能底层心法"

  1. 跨平台写C++时:永远用 compare_exchange_weak(而非 strong),因为它在ARM上映射为LL/SC的一次尝试,失败后让C++循环重试;而 strong 在ARM上会编译成LL/SC+内部自旋,性能反而差。
  2. 在x86上:weak 和 strong 没区别(底层都是 cmpxchg)。
  3. 检测CPU特性:
    // 运行时检测 ARM LSE 是否支持 (Linux)
    if (getauxval(AT_HWCAP) & HWCAP_ATOMICS) {
        // 使用原生 CAS,性能起飞
    }