简体   繁体   English

std::atomic_ref 的替代品

[英]Alternatives to std::atomic_ref

I'm looking for an alternative to std::atomic_ref which might be used without needing C++20 support.我正在寻找可以在不需要 C++20 支持的情况下使用的std::atomic_ref的替代方案。 I've considered casting pointers to std::atomic but that does not seem like a safe option.我考虑过将指针转换为std::atomic ,但这似乎不是一个安全的选择。

The use-case would be to apply atomic operations to non-atomic objects for the lifetime of the atomic reference to avoid race.用例是在原子引用的生命周期内将原子操作应用于非原子对象以避免竞争。 The objects accessed cannot all be made atomic, so a wrapper like the atomic_ref would be needed.访问的对象不能都是原子的,因此需要像 atomic_ref 这样的包装器。

Any help is appreciated!任何帮助表示赞赏!

If your compiler supports OpenMP (most of them do), you can mark your object access with #pragma atomic .如果您的编译器支持 OpenMP(大多数都支持),您可以使用#pragma atomic标记您的 object 访问权限。 Possibly with a proper operation ( read , write , update , capture ) and memory-ordering semantics.可能具有适当的操作( readwriteupdatecapture )和内存排序语义。

EDIT编辑

Alternatively, it also seems that Boost provides atomic_ref available to pre-C++20 codes: https://www.boost.org/doc/libs/1_75_0/doc/html/atomic/interface.html#atomic.interface.interface_atomic_ref或者,Boost 似乎也为 C++20 之前的代码提供了atomic_refhttps://www.boost.org/doc/libs/1_75_0/doc/html/atomic/interface.html#atomic.interface.interface_atomic_ref

Another way might be casting a non-atomic object into an atomic one by using reinterpret_cast .另一种方法可能是使用reinterpret_cast将非原子 object 转换为原子。 This solution will likely cause undefined behavior, but may actually work with some implementations.此解决方案可能会导致未定义的行为,但实际上可能适用于某些实现。 It is, for instance, used in the Facebook Folly library: https://github.com/facebook/folly/blob/master/folly/synchronization/PicoSpinLock.h#L95 .例如,它在 Facebook Folly 库中使用: https://github.com/facebook/folly/blob/master/folly/synchronization/PicoSpinLock.h#L95

In GCC/clang (and other compilers that implement GNU C extensions), you can use the __atomic builtins , such as在 GCC/clang(以及其他实现 GNU C 扩展的编译器)中,您可以使用__atomic内置函数,例如

int load_result = __atomic_load_n(&plain_int_var, __ATOMIC_ACQUIRE);

That's how atomic_ref<T> is implemented on such compilers: just wrappers for those builtins.这就是atomic_ref<T>在此类编译器上实现的方式:只是那些内置函数的包装器。 (That's why atomic_ref is super light-weight, and it's normally best to construct one for free every time you need one, not keep around a single atomic_ref .) (这就是为什么atomic_ref是超轻量级的,通常最好在每次需要时免费构建一个,而不是只保留一个atomic_ref 。)

Since you won't have std::atomic_ref<T>::required_alignment , it's normally sufficient to give the objects natural alignment, ie alignas( sizeof(T) ) T foo;因为你不会有std::atomic_ref<T>::required_alignment ,通常给对象自然 alignment 就足够了,即alignas( sizeof(T) ) T foo; to make sure __atomic operations are actually atomic, as well as having memory-order guarantees.确保__atomic操作实际上是原子的,并且具有内存顺序保证。 (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 , finally fixed in 2020, although it only affected struct members.) (在许多实现中,所有支持无锁原子的普通T都已经获得足够的 alignment,但例如某些 32 位系统仅将int64_t对齐 4 个字节,但 8 字节原子仅与 8 字节 alignment 对齐。 x86 gcc -m32 had a problem with this in C++ for a while , and for a lot longer with _Atomic in C , finally fixed in 2020, although it only affected struct members.)


reinterpret_cast< std::atomic<T>* > may actually work in practice on most compilers , maybe not even being UB depending on the internals of atomic<> . reinterpret_cast< std::atomic<T>* >实际上可以在大多数编译器上实际工作,甚至可能不是 UB,具体取决于atomic<>的内部结构。

(Most?) other compilers implement atomic (and atomic_ref) in a way that's similar to GNU C, I think, using builtin functions. (大多数?)其他编译器使用内置函数以类似于 GNU C 的方式实现原子(和 atomic_ref)。 eg for MSVC, something like _InterlockedExchange() to implement atomic<>::exchange .例如对于 MSVC,类似_InterlockedExchange()来实现atomic<>::exchange

In mainstream C++ implementations, atomic<T> has the same size and layout as a plain T .在主流 C++ 实现中, atomic<T>具有与普通T相同的大小和布局。 (The size is something you can static_assert ) It's in theory possible for a non-lock-free atomic<> to include a mutex or something, but normal implementations don't ( Where is the lock for a std::atomic? ). (大小是你可以static_assert的东西)理论上非无锁atomic<>可以包含互斥锁或其他东西,但正常的实现不会( std::atomic的锁在哪里? )。 (Partly for compat with C11 _Atomic , which IIRC has some requirements about even uninitialized or maybe zero-initialized objects still working properly. But also just for size reasons.) (部分是为了与 C11 _Atomic兼容,IIRC 对即使未初始化或零初始化的对象仍能正常工作有一些要求。但也只是出于大小原因。)

Despite ISO C++ not guaranteeing that it's well-defined, you will basically end up calling __atomic_fetch_add_n or InterlockedAdd on an int member var of atomic<int> with the same address as your original plain int .尽管 ISO C++ 不能保证它是明确定义的,但您基本上最终会在atomic<int>int成员 var 上调用__atomic_fetch_add_nInterlockedAdd ,其地址与原始普通int相同。

That might still technically be UB;从技术上讲,这可能仍然是UB; there's a rule about structs being compatible up to the first difference in their definition, but I'm less sure about an int* into a struct or especially a struct{int;}* pointer to an int object.有一条关于结构兼容的规则,直到它们的定义中的第一个差异,但我不太确定int*到结构中,特别是struct{int;}*指向int object 的指针。 I think that violates the strict-aliasing rule.我认为这违反了严格的混叠规则。

But I think still unlikely to break in practice.但我认为仍然不太可能在实践中打破。 Still, the possible breakage would only show up under optimization, and be dependent on surrounding code, meaning it's not something you can easily write a unit-test for.尽管如此,可能的破坏只会在优化下出现,并且依赖于周围的代码,这意味着你不能轻易地为它编写单元测试。

However, the most likely-to-break scenario would be if the same function (after inlining) was reading or writing the plain variable mixed with operations on the same variable through an atomic<>* or atomic<>& reference.但是,最有可能中断的情况是,如果相同的 function(内联后)正在读取或写入普通变量,并通过atomic<>*atomic<>&引用对同一变量进行操作。 Especially if there isn't any kind of memory barrier separating those accesses, such as calling some_thread.join() .特别是如果没有任何类型的 memory 屏障分隔这些访问,例如调用some_thread.join() If you mixing atomic and non-atomic access within one function (after inlining), this may be safe and portable enough to work until you can use atomic_ref<> properly.如果您在一个 function (内联后)中混合原子和非原子访问,这可能足够安全和便携,直到您可以正确使用atomic_ref<>为止。


The other good short-term option is manually using either GNU C or MSVC atomic builtins directly, if your source code currently only cares about one or the other.如果您的源代码目前只关心其中一个,则另一个好的短期选项是直接手动使用 GNU C 或 MSVC atomic builtins。 Or roll your own (limited subset of) atomic_ref using the versions of these functions you actually need.或者使用您实际需要的这些函数的版本来滚动您自己的(有限子集) atomic_ref

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM