有人能告诉我,std::atomic<T>::is_lock_free()
是不是静态的,还是 constexpr 的?它如果不是静态的和/或不是 constexpr 的话对我来说就没有意义。
为什么它没有像 C++17 中的 is_always_lock_free
那样设计呢?
有人能告诉我,std::atomic<T>::is_lock_free()
是不是静态的,还是 constexpr 的?它如果不是静态的和/或不是 constexpr 的话对我来说就没有意义。
为什么它没有像 C++17 中的 is_always_lock_free
那样设计呢?
如在cppreference中所解释:
所有原子类型都可以使用互斥锁或其他锁定操作进行实现,但std::atomic_flag除外。例如,在某些架构上,只有对齐的内存访问才是本质上原子的,因此同一类型的未对齐对象必须使用锁。
C++标准建议(但不要求)无锁原子操作也是地址自由的,即适用于使用共享内存进行进程间通信。
正如多个其他人所提到的,std::is_always_lock_free
可能是您真正需要的。
编辑:为了澄清,C++对象类型有一个对地址进行限制的对齐值,其实例的地址仅限于2的幂次倍数([basic.align]
)。
alignof(double) == sizeof(double) == 8
,因为不对齐访问有许多缺点(速度、缓存、原子性...)。但是例如 #pragma pack(1) struct X { char a; double b; };
或者 alignas(1) double x;
允许您拥有"不对齐"的double
。因此,当cppreference谈论"对齐的内存访问"时,它可能是以硬件类型的自然对齐方式来表述的,并没有违反其对齐要求使用C++类型的方式(这将是UB)。alignof(double)==4
。但是std::atomic<double>
仍然具有alignof()=8
而不是在运行时检查对齐方式。使用下对齐的打包结构会破坏ABI,并且不受支持。(GCC对于32位x86更喜欢为8字节的对象提供自然对齐,但是结构打包规则会覆盖这一点,只基于alignof(T)
,例如在i386 System V上。G++曾经有一个错误,在结构体内部的atomic<int64_t>
可能不是原子的,因为它只是假设。GCC(对于C而不是C++)仍然存在这个错误!) - Peter Cordesstd::atomic_ref<double>
的正确实现将完全拒绝不对齐的double
,或者在平台上运行时检查对齐方式,在这些平台上,普通的double
和int64_t
可以小于自然对齐方式。(因为atomic_ref<T>
操作的是声明为普通T
的对象,并且只有最小对齐方式为alignof(T)
,没有机会给它额外的对齐方式。) - Peter Cordesgcc -m32
编译时显示出_Atomic int64_t
的撕裂。无论如何,我的观点是真正的编译器不支持未对齐的原子操作,并且不进行运行时检查(尚未?),因此**#pragma pack
或__attribute__((packed))
只会导致非原子性;对象仍将报告它们是“lock_free”。** - Peter Cordesis_lock_free()
的目的是允许实现与当前实际情况不同的工作方式;通过基于实际对齐方式的运行时检查来使用硬件支持的原子指令或使用锁。 - Peter Cordesstd::atomic_ref
,您需要自己确保它的对齐方式正确,例如alignas(std::atomic_ref<double>::required_alignment) double foo
。否则会产生未定义行为,因此atomic_ref
不需要检查对齐或处理对齐不正确的对象。https://en.cppreference.com/w/cpp/atomic/atomic_ref/required_alignment - undefined您可以使用std::is_always_lock_free
。
is_lock_free
取决于实际的系统,无法在编译时确定。
相关解释:
原子类型有时也被允许是无锁的,例如,在某个架构上仅对齐的内存访问自然是原子性的,未对齐的相同类型对象必须使用锁。
std::numeric_limits<int>::max
取决于架构,但它是静态和 constexpr
的。我猜答案没有问题,但我不相信推理的第一部分。 - 463035818_is_not_a_numberis_lock_free
在该编译器上是无意义的。 - Max Langhofstd::atomic<T>::is_lock_free()
可能会在某些实现中根据运行时条件返回true
或false
。
正如Peter Cordes在评论中指出的那样,运行时条件不是对齐,因为原子操作将(过度)对齐内部存储以进行高效的无锁操作,并强制不对齐会导致原子性丢失,这是未定义行为。
可能会有一种实现方式,它不会强制执行对齐,并且会基于对齐进行运行时调度,但这不是一个合理的实现方式。仅当__STDCPP_DEFAULT_NEW_ALIGNMENT__
小于所需的原子对齐时,才支持pre-C++17,因为动态分配的超对齐在C++17之前不起作用。
另一个可能决定原子性的运行时条件是运行时CPU调度。
在x86-64上,实现可以通过初始化期间的cpuid
检测cmpxchg16b
的存在,并将其用于128位原子操作,对于32位的cmpxchg8b
和64位原子操作也同样适用。如果找不到相应的cmpxchg
,则无法实现无锁原子性,实现将使用锁。
MSVC目前不进行运行时CPU调度。由于ABI兼容性原因,它不会对64位进行调度,由于已经不支持没有cmpxchg8b
的CPU,因此也不会对32位进行调度。Boost.Atomic默认情况下不执行此操作(假设存在cmpxchg8b
和cmpxhg16b
),但可以进行配置以进行检测。我还没有研究其他实现的情况。
std::atomic<>::is_lock_free()
API 允许实现中存在 alignof(std::atomic<T>)
小于 sizeof
的情况。目前的实现选择使 alignof == sizeof,因此它们不需要运行时对齐检查。这意味着在未对齐的 atomic<T>
对象上调用 is_lock_free
或任何其他成员函数是未定义的,因此返回值无关紧要。总之,这是实现的选择,而不是 ISO C++11 的约束。(虽然这是一个很好且显然的实现选择!)另外,关于运行时分派的观点也很有道理。 - Peter Cordesnew
的对齐方式被固定为__STDCPP_DEFAULT_NEW_ALIGNMENT__
,不能通过alignas
增加。我不认为有些实现使用比最大无锁原子所需的更小的分配对齐方式,但这似乎是提供标准处理此问题的原因。 - Alex Gutenievnew
的有趣观点。您可以考虑针对最大对象大小进行运行时对齐检查(特别是如果需要原子 RMW 仅用于读取),而不仅仅是决定它永远不会 lock_free,如果 new
对齐小于该大小。在任何主流的 x86 实现上都不是这种情况,例如我认为 MSVC 在 x86-64 上按 16 对齐(GNU/Linux 当然也是如此),并且在 32 位模式下所有内容都至少按 8 对齐。我不知道 gcc 在 AArch64 / MIPS64 / PPC64 上的 alignof(max_align_t) 是多少。我认为 AArch64 将具有 16 字节的原子基线,甚至不需要 -march
选项,但可能是 16B new。 - Peter Cordesldp
或stp
(32位寄存器的加载对或存储对)自然对齐时,也只能保证是原子操作。
所以我写了一个小程序来检查任意原子指针返回的is_lock_free()值。以下是代码:
#include <atomic>
#include <cstddef>
using namespace std;
bool isLockFreeAtomic( atomic<uint64_t> *a64 )
{
return a64->is_lock_free();
}
|?isLockFreeAtomic@@YA_NPAU?$atomic@_K@std@@@Z| PROC
movs r0,#1
bx lr
ENDP
这只是返回true
,也就是1
。
这个实现选择使用alignof( atomic<int64_t> ) == 8
,因此每个atomic<int64_t>
都被正确对齐。这避免了在每次加载和存储时需要运行时对齐检查的需要。
(编辑者注:这是很常见的;大多数实际的C++实现都是这样工作的。这就是为什么std::is_always_lock_free
非常有用的原因,因为它通常适用于is_lock_free()
为真的类型。)
atomic<uint64_t>
和alignof() == 8
,这样他们就不必在运行时检查对齐方式。这个旧的API给了他们不这样做的选项,但在当前的硬件上,要求对齐更有意义(否则UB,例如非原子性)。即使在32位代码中,int64_t
可能只有4字节对齐,atomic<int64_t>
也需要8字节。请参见我在另一个答案中的评论。 - Peter Cordesalignof
值与硬件的“良好”对齐方式相同,那么is_lock_free
将始终为true
(is_always_lock_free
也是如此)。你的编译器正是这样做的。但是API存在的目的是让其他编译器可以执行不同的操作。 - Max Langhofalignof(std::atomic<double>) == 1
的内容(因此在 C++ 中不会有“未对齐访问”,因此也不会有 UB),即使硬件只能保证在 4 或 8 字节边界上进行无锁原子操作。编译器将在未对齐的情况下使用锁定(并根据对象实例的内存位置返回适当的布尔值从 is_lock_free
中)。 - Max Langhofstd::atomic<>::is_lock_free()
API被设计成允许这种实现选择。但在实际的应用中,这将是一个糟糕的选择。调用它时,如果std::atomic<>
对象的对齐方式小于其alignof
,已经属于未定义行为,因此它仍然返回true并不违反任何规定,只是意味着该API无法检测到该问题。 - Peter Cordes
is_always_lock_free
吗? - Mike van Dyke