std::atomic_ref 的替代品

Alternatives to std::atomic_ref

我正在寻找 std::atomic_ref 的替代方案,无需 C++20 支持即可使用。我考虑过将指针转换为 std::atomic,但这似乎不是一个安全的选择。

用例是在原子引用的生命周期内将原子操作应用于非原子对象以避免竞争。访问的对象不能全部成为原子的,因此需要像 atomic_ref 这样的包装器。

感谢任何帮助!

如果您的编译器支持 OpenMP(大多数支持),您可以使用 #pragma atomic 标记您的对象访问。可能需要适当的操作(readwriteupdatecapture)和内存排序语义。

编辑

或者,Boost 似乎还提供了 atomic_ref 可用于 C++20 之前的代码:https://www.boost.org/doc/libs/1_75_0/doc/html/atomic/interface.html#atomic.interface.interface_atomic_ref

另一种方法可能是使用 reinterpret_cast 将非原子对象转换为原子对象。此解决方案可能会导致未定义的行为,但实际上可能适用于某些实现。例如,它在 Facebook Folly 库中使用:https://github.com/facebook/folly/blob/master/folly/synchronization/PicoSpinLock.h#L95.

在GCC/clang(以及其他实现GNU C扩展的编译器)中,你可以使用__atomic builtins,比如

int load_result = __atomic_load_n(&plain_int_var, __ATOMIC_ACQUIRE);

这就是 atomic_ref<T> 在此类编译器上的实现方式:只是那些内置函数的包装器。 (这就是 atomic_ref 超轻的原因,通常最好每次需要时免费构建一个,而不是随身携带一个 atomic_ref。)

因为你不会有 std::atomic_ref<T>::required_alignment, it's normally sufficient to give the objects natural alignment, i.e. alignas( sizeof(T) ) T foo; to make sure __atomic operations are actually atomic, as well as having memory-order guarantees. (On many implementations, all plain T that support lock-free atomics at all already get sufficient alignment, but for example some 32-bit systems only align int64_t by 4 bytes, but 8-byte atomics are only atomic with 8-byte alignment. x86 gcc -m32 had a problem with this in C++ for a while, and for a lot longer with _Atomic in C,最终在 2020 年修复,虽然它只影响结构成员。)


reinterpret_cast< std::atomic<T>* > 实际上可能在大多数编译器上都有效,甚至可能不是 UB,这取决于 atomic<>.

的内部结构

(大多数?)其他编译器使用内置函数以类似于 GNU C 的方式实现原子(和 atomic_ref)。例如对于 MSVC,类似 _InterlockedExchange() 的东西可以实现 atomic<>::exchange.

在主流的 C++ 实现中,atomic<T> 与普通的 T 具有相同的大小和布局。 (大小是你可以 static_assert 理论上非无锁 atomic<> 可以包含互斥锁或其他东西,但正常实现不会(). (部分是为了与 C11 _Atomic 兼容,IIRC 对甚至未初始化或零初始化的对象仍能正常工作有一些要求。但也只是出于大小原因。)

尽管 ISO C++ 不保证它定义明确,但您基本上最终会在 atomic<int>int 成员变量上调用 __atomic_fetch_add_nInterlockedAdd与您原来的普通地址相同 int.

从技术上讲,这可能仍然是 UB;有一个关于结构在定义中的第一个差异是兼容的规则,但我不太确定 int* 到结构中,尤其是 struct{int;}* 指向 int 对象的指针。我认为这违反了严格的别名规则。

但我认为在实践中还是不太可能破解。尽管如此,可能的破坏只会在优化下出现,并且取决于周围的代码,这意味着它不是您可以轻松为其编写单元测试的东西。

但是,最有可能出现中断的情况是同一函数(内联后)读取或写入普通变量 与对同一变量的 操作混合atomic<>*atomic<>& 参考。特别是如果没有任何类型的内存屏障来分隔这些访问,例如调用 some_thread.join()如果您在一个函数中混合原子和非原子访问(内联后),这可能是安全和可移植的,足以工作,直到您可以正确使用 atomic_ref<>


另一个好的短期选择是直接手动使用 GNU C 或 MSVC 原子内置函数,如果您的源代码当前只关心其中之一。 或者滚动您的拥有(有限的)atomic_ref 使用您实际需要的这些函数的版本。