四大经典同步算法不直接竞争总线锁,真正触发硬件同步的是CMPXCHG、XCHG、LOCK ADD等RMW指令,其性能瓶颈源于多核间缓存行高频迁移与伪共享,而非总线锁定本身。

四大算法本身并不直接“竞争总线锁”,这个说法存在概念错位。真正参与总线锁(或更准确地说,缓存行级锁定)的是底层原子指令,而算法只是调用它们的上层逻辑。硬核解释的关键,在于厘清:哪些指令会触发硬件同步机制、它们在多核环境下的真实行为、以及为何某些算法模式会让这种机制高频暴露。
哪些汇编指令真正牵动总线或缓存一致性协议
CPU 不会因为执行普通 mov、add、cmp 就锁总线。真正触发硬件级同步保障的,是明确具备“读-改-写”(Read-Modify-Write, RMW)语义的原子指令:
- CMPXCHG(x86/x64):比较并交换,CAS 的硬件实现。执行时必须确保“读取旧值→比对→写入新值”三步不可分割。现代 CPU 通常通过独占监控(如 x86 的 LOCK# 信号或 ARM 的 LDAXR/STLXR)完成,不锁总线,但会将目标缓存行置为 Exclusive 或 Modified 状态,并广播 RFO(Read For Ownership)请求。
- XCHG(x86/x64):交换指令。当操作内存时默认带 LOCK 前缀,等效于原子 swap。它会强制发起总线锁或缓存行锁定,是历史最久、开销最大的原子原语之一。
- LOCK ADD / LOCK INC(x86/x64):带 LOCK 前缀的算术指令。在较老 CPU 或未命中缓存时可能退化为总线锁定;在现代 CPU 上,若目标已在本核缓存且处于 Exclusive 状态,则仅需缓存一致性协议协调,避免总线争用。
- ARM 的 LDAXR + STLXR 组合:不是单条指令,而是一对协作指令。LDAXR 标记某地址为“独占访问”,STLXR 尝试写入——仅当期间无其他核修改该缓存行才成功。失败时软件需重试。整个过程完全基于缓存行状态机(MESI/MOESI),不涉及总线锁。
所谓“激烈竞争”,其实是缓存行在多核间高频迁移
总线锁早已不是主流方案。现代多核系统中,“竞争激烈”的本质是多个核心反复争夺同一缓存行的独占权(Exclusive 或 Modified 状态)。例如:
- 一个全局计数器变量被 8 个线程高频 CAS 更新,该变量所在的 64 字节缓存行就会在 L1 缓存间频繁传递(Cache Line Ping-Pong);
- 每个 CAS 失败后重试,都触发一次 RFO 请求和无效化(Invalidate)广播,消耗互连带宽;
- 若该变量与其他热点数据(如循环变量、局部数组)落在同一缓存行,还会引发“伪共享”(False Sharing),让无关写操作也触发缓存行失效——这才是性能杀手,而非总线锁本身。
四大经典同步算法的硬件行为差异
所谓“四大算法”(如 Peterson、Dekker、Test-and-Set、Compare-and-Swap)在汇编层面体现为不同指令序列与内存屏障组合,其对硬件资源的压力截然不同:
- Test-and-Set(TAS):常由 XCHG 实现。每次尝试都强制获取缓存行所有权,失败即自旋重试。在高争用下,产生大量 RFO 流量,是缓存一致性协议的“压力测试仪”。
- Peterson / Dekker 算法:纯软件实现,依赖 volatile 读写 + 内存屏障(如 mfence / dmb)。不使用 RMW 指令,因此不触发 RFO 或总线锁,但需严格保证 StoreLoad/LoadStore 屏障防止编译器/CPU 重排——否则逻辑崩溃。
- CAS 循环(如 Java AQS 中的 acquire):核心是 CMPXCHG。现代 JVM 会生成带 lock 前缀的指令(x86)或 LDAXR/STLXR(ARM)。它比 TAS 更友好:只有写操作失败时才需重试,读操作可并发进行。
- ticket lock(队列锁):含两次原子操作——先 fetch-and-add 获取 ticket,再 while (current != my_ticket) 自旋读。前者是 RMW,后者是普通 load。它把争用从“写竞争”转为“读广播”,大幅降低 RFO 压力,是缓存友好的典型设计。
如何观测与验证
真正在硬件层面看竞争,不能靠反汇编,而要借助性能监控单元(PMU):
- x86 上用 perf record -e cycles,instructions,mem_load_retired.l1_miss,mem_inst_retired.all_stores,l1d.replacement -a sleep 1,重点关注 l1d.replacement(L1 数据缓存替换次数)和 mem_load_retired.l1_miss(L1 加载未命中)——飙升说明缓存行在抖动;
- 观察 cpu_clk_unhalted.thread 与 l2_rqsts.demand_data_rd 比值,若后者占比异常高,表明大量跨核数据拉取;
- ARM 平台可用 pmu-event-list 中的 l3d_cache_refill 和 l3d_cache 事件定位缓存行争用热点。
不复杂但容易忽略:总线锁是上世纪的解决方案,今天真正的瓶颈藏在缓存一致性协议的握手开销里。优化方向从来不是“减少原子指令”,而是“减少对同一缓存行的写竞争”——比如用 padding 消除伪共享、用分段计数器替代全局计数器、用 MCS 或 CLH 队列锁代替 TAS。

















