无锁原子在实践中是无地址的吗?

Are lock-free atomics address-free in practice?

Boost.Interprocess 是一个很棒的库,它简化了不同进程之间共享内存的使用。它提供了互斥量、条件变量和信号量,允许在从共享内存写入和读取时进行同步。

但是,在某些情况下,这些(相对)性能密集型同步机制不是必需的 - 原子操作足以满足我的用例,并且可能会提供更好的性能。

不幸的是,Boost.Interprocess 似乎没有原子。


C++ 标准库提供 std::atomic class template, which encapsulates objects whose operations need to be atomic, and also has functions to test if atomic operations are lock-free. But it does not require lock-free atomics to be address-free as well: [atomics.lockfree]/4 merely encourages that lock-free operations be address-free, and this is in agreement with cppreference

我想不出任何人为什么会以非无地址方式实现无锁原子。在我看来,以无地址方式实现无锁原子甚至要容易得多。

由于在使用原子而不是互斥锁时我会获得显着的性能优势(来自 Boost.Interprocess),因此似乎很想在这里降低标准合规性并将 std::atomic 个对象存储在共享内存中。


这个问题分为两部分:

  1. CPU 在实践中是否以无地址方式实现无锁原子? (我只关心用于 运行 现代桌面和移动操作系统(例如 Windows、MacOS、Linux、Android、iOS)的 CPU,但不是嵌入式系统)
  2. 到底为什么一个实现会使用无锁的非无地址原子?

是的,无锁原子在所有正常 CPUs 上的所有 C++ 实现上都是无地址的,并且可以安全地用于进程之间的共享内存。 但是,非无锁原子1 不会 在进程之间是安全的。每个进程都有自己的 table 锁散列 ()。

C++ 标准旨在使无锁原子在进程之间的共享内存中工作,但它只能达到“应该”的程度,而没有定义术语等。

C++draft 29.5 Lock-free property

[ Note: Operations that are lock-free should also be address-free. That is, atomic operations on the same memory location via two different addresses will communicate atomically. The implementation should not depend on any per-process state. This restriction enables communication by memory that is mapped into a process more than once and by memory that is shared between two processes. — end note ]

这是一个非常容易在当前硬件上实现的实现质量建议,事实上你必须努力设计一个 deathstation9000 C++ 实现,在 x86 / ARM / PowerPC /其他主流 CPU 而实际上是无锁的。


硬件为原子读取-修改-写入操作公开的机制基于 MESI 缓存一致性,它只关心物理地址。 x86 lock cmpxchg / lock add / etc. 使核心挂在修改状态的缓存行上,因此在原子操作的中间没有其他核心可以 read/write 它。 ().

大多数非 x86 架构使用 LL/SC,它允许您编写一个重试循环,该循环仅在存储是原子的时才进行。 LL/SC 可以在不引入地址的情况下以无等待的方式模拟具有 O(1) 开销的 CAS。

C++ 无锁原子编译为直接使用 LL/SC 指令。请参阅我对 x86 示例的 num++ 问题的回答。有关使用 LL/SC 指令的 compare_exchange_weakfetch_add 的 AArch64 代码生成示例,请参阅

原子纯加载或纯存储更容易,并且使用对齐数据免费发生。在 x86 上,请参阅 其他 ISA 也有类似的规则。


相关:我在 的回答中包含了一些关于无地址的评论。我不确定它们是否有帮助或正确。 :/


脚注 1:

所有主流 CPU 都为指针宽度的对象提供无锁原子。有些具有更宽的原子性,例如 x86 具有 lock cmpxchg16b,但并非所有实现都选择将其用于双宽度无锁原子对象。检查 C++17 std::atomic::is_always_lock_freeATOMIC_xxx_LOCK_FREE(如果已定义)以进行编译时检测。

(一些微控制器不能在单个寄存器中保存指针(或通过单个操作将其复制),但通常没有此类 ISA 的多核实现。)


Why on earth would an implementation use non-address-free atomics that are lock-free?

我不知道硬件有什么合理的理由可以像普通的现代 CPUs 那样工作。您可以想象一些架构,您可以通过将地址提交给某些

来执行原子操作

我觉得C++标准是想尽可能避免约束非主流的实现。例如C++ 在某种解释器之上,而不是为“正常”CPU 架构编译机器代码。

IDK 如果你能在松散耦合的共享内存系统上有用地实现 C++,比如带有以太网链接而不是共享内存的集群,或者非一致的共享内存(必须显式刷新以便其他线程看到你的商店)。

我认为主要是 C++ 委员会不能多说原子 必须 是如何实现的,而不假设实现将 运行 下的程序 [=84] =] 其中多个进程可以设置共享内存。

他们可能在想象未来的一些 ISA,其中无地址原子是不可能的,但我认为他们更有可能不想谈论多个 C++ 程序之间的共享内存。该标准仅要求实现 运行 一个程序。

显然 std::atomic_flag 实际上保证是无地址的 ,所以 IDK 为什么他们不对实现选择实现的任何 atomic<T> 提出相同的要求无锁。