Arm64平台下TTAS自旋锁中memory_order_relaxed为何足够?
struct spinlock { std::atomic<bool> lock_ = {0}; void lock() noexcept { for (;;) { if (!lock_.exchange(true, std::memory_order_acquire)) { return; } while (lock_.load(std::memory_order_relaxed)) { // what's the guarantee???? asm volatile ("yield\nyield\nyield"); } } } bool try_lock() noexcept { return !lock_.load(std::memory_order_relaxed) && // why not acquire??? !lock_.exchange(true, std::memory_order_acquire); } void unlock() noexcept { lock_.store(false, std::memory_order_release); } };
结论:这个实现不是bug,你的理解存在偏差
我们拆解核心逻辑逐个解释疑问:
1. 自旋阶段的memory_order_relaxed加载为什么没问题?
自旋阶段的load(relaxed)只是用来忙等锁释放的信号,它不需要承担同步语义的职责:
- 真正保证内存可见性和同步的是
lock里的exchange(acquire)和unlock里的store(release):release会确保持有锁线程的所有操作都对后续获取锁的线程可见,acquire会确保获取锁后能看到之前所有release同步的操作。 - 哪怕在Arm64上,
relaxed加载不会主动刷新失效队列,但硬件的缓存一致性协议(比如Arm的MOESI)最终会把锁的更新同步到所有核心。自旋线程可能会短暂读到旧值,但绝不会一直卡住——当持有锁的线程执行store(false, release)后,这个更新最终会被所有核心感知到。 - 用
relaxed是性能优化:自旋阶段会频繁执行加载操作,acquire加载带有内存屏障开销,会拖累自旋效率,而relaxed是最轻量的加载,完全满足“能最终看到锁释放”的需求。
2. try_lock里的relaxed加载为什么没问题?
这里的load(relaxed)是一个快速路径优化:
- 它只是提前判断锁是否可能空闲,如果读到
false,再通过exchange(acquire)做原子性的锁获取——exchange本身是原子操作,并且带有acquire语义,会确保后续操作的同步正确性。 - 哪怕
relaxed加载读到了过期的false(实际锁已经被持有),后面的exchange也会正确返回true,导致try_lock返回false,不会出现错误的锁获取。这个优化只是为了避免在锁被持有时执行昂贵的exchange操作,完全不影响正确性。
内容的提问来源于stack exchange,提问作者blonded04
相关产品推荐
相关产品推荐

