[英]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.可能具有适当的操作(
read
、 write
、 update
、 capture
)和内存排序语义。
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_ref
: https://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_n
或InterlockedAdd
,其地址与原始普通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
。
_InterlockedAdd
(the intrinsic/built-in function) https://docs.microsoft.com/en-us/previous-versions/51s265a6(v=vs.85) _InterlockedAdd
(内在/内置函数) https://docs.microsoft.com/en-us/previous-versions/51s265a6(v=vs.85)InterlockedAdd
(the Windows library function) https://docs.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-interlockedadd InterlockedAdd
(Windows 库函数) https://docs.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-interlockedadd
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.