为什么std :: atomic <T> :: is_lock_free()和constexpr一样不是静态的?


9

谁能告诉我std :: atomic :: is_lock_free()是否像constexpr一样不是静态的?使它为非静态和/或为非constexpr对我来说没有意义。



3
我要丢掉“对齐”。
Max Langhof

@MaxLanghof您是否意味着并非所有实例都将以相同的方式对齐?
curiousguy19年

1
迈克,不,我不知道,但是谢谢你的提示。这对我很有帮助。但是我问自己,为什么在is_lock_free()和is_always_lock_free之间有一个决定。这不是由于未对齐的原子引起的,在这里有人建议,因为该语言将未对齐的访问定义为始终具有未定义的行为。
Bonita Montero,

Answers:


10

如对cppreference的解释:

除std :: atomic_flag以外的所有原子类型都可以使用互斥锁或其他锁定操作来实现,而不是使用无锁原子CPU指令来实现。原子类型有时也可以是无锁的,例如,如果在给定的体系结构上,只有对齐的内存访问自然是原子的,则相同类型的未对齐对象必须使用锁。

C ++标准建议(但不要求)无锁原子操作也无地址,也就是说,适用于使用共享内存的进程之间的通信。

正如其他许多人提到的那样,std::is_always_lock_free可能正是您真正想要的。


编辑:明确起见,C ++对象类型具有一个对齐值,该对齐值将其实例的地址限制为仅2的幂的某些倍数[basic.align]。这些对齐值是针对基本类型的实现定义的,不需要等于类型的大小。它们也可能比硬件实际支持的条件更严格。

例如,x86(大多数情况下)支持不对齐的访问。但是,您会发现大多数alignof(double) == sizeof(double) == 8x86 编译器都具有x86,因为未对齐的访问具有许多缺点(速度,缓存,原子性...)。但是,例如#pragma pack(1) struct X { char a; double b; };alignas(1) double x;允许您具有“ unaligned” double。因此,当cppreference谈论“对齐的内存访问”时,大概是根据硬件类型的自然对齐方式进行的,而不是以与其对齐要求(即UB)相抵触的方式使用C ++类型。

这是更多信息:在x86上成功进行未对齐访问的实际影响是什么?

还请在下面查看@Peter Cordes的有见地的评论!


1
32位x86是一个很好的示例,可以在其中找到ABI alignof(double)==4。但是std::atomic<double>仍然alignof() = 8没有在运行时检查对齐方式。使用原子未对齐的压缩结构会破坏ABI,因此不受支持。(适用于32位x86的GCC倾向于自然对齐8字节对象,但结构打包规则会覆盖该对齐方式,并且仅基于alignof(T),例如基于i386 SystemV。G ++曾经存在一个错误,其中atomic<int64_t>结构内部可能不是原子的因为它只是假设而已。GCC(对于C而不是C ++)仍然存在此错误!)
Peter Cordes

2
但是C ++ 20的正确实现std::atomic_ref<double>将要么double完全拒绝未对齐的内容,要么在运行时在合法的平台上检查对齐情况,该平台应为纯文本doubleint64_t小于自然对齐的地方。(因为atomic_ref<T>在声明为普通对象的对象上进行操作T,并且其最小对齐方式为,alignof(T)没有机会对其进行额外对齐。)
Peter Cordes

2
gcc.gnu.org/bugzilla/show_bug.cgi?id=62259为现在固定的libstdc ++错误,和gcc.gnu.org/bugzilla/show_bug.cgi?id=65146用于静止破碎C缺陷,包括纯ISO C11测试用例,_Atomic int64_t用当前编译时显示撕裂了gcc -m32。无论如何,我的观点是,真正的编译器不支持未对齐的原子,也不进行运行时检查(还可以吗?),所以#pragma pack还是__attribute__((packed))会导致非原子性;对象仍会报告它们是lock_free
彼得·科德斯

1
但是,是的,目的is_lock_free()允许实现与当前的实现方式不同。通过基于实际对齐的运行时检查来使用硬件支持的原子指令或使用锁。
彼得·科德斯

3

您可以使用 std::is_always_lock_free

is_lock_free 取决于实际系统,无法在编译时确定。

相关说明:

原子类型有时也可以是无锁的,例如,如果在给定的体系结构上,只有对齐的内存访问自然是原子的,则相同类型的未对齐对象必须使用锁。


1
std::numeric_limits<int>::max取决于体系结构,但是是静态和constexpr。我猜答案没有错,但是我不购买推理的第一部分
idclev 463035818

1
难道不将语言未对齐访问定义为具有未定义的行为,这样在运行时对锁自由度的评估是没有意义的吗?
Bonita Montero,

1
在对齐访问和未对齐访问之间做出决定是没有意义的,因为该语言将后者定义为未定义行为。
Bonita Montero,

@BonitaMontero有“未对齐C ++对象对齐”的意思和“未按硬件喜欢的对齐”的意思。这些不一定相同,但实际上它们经常相同。你展示的例子就是这样一个实例,其中编译器显然有内置的假设,这两个相同的-这只是意味着is_lock_free是毫无意义上的编译器
Max Langhof

1
您可以肯定地确定,如果有对齐要求,则原子将具有正确的对齐方式。
Bonita Montero,

1

我已经在Windows PC上安装了Visual Studio 2019,并且该devenv也具有ARMv8编译器。ARMv8允许未对齐的访问,但是比较和交换,锁定的添加等必须进行对齐。并且还纯加载/存储纯使用ldpstp(负载对或商店对32位寄存器)只保证是原子时,他们自然对齐。

因此,我编写了一个小程序来检查is_lock_free()返回的任意原子指针。所以这是代码:

#include <atomic>
#include <cstddef>

using namespace std;

bool isLockFreeAtomic( atomic<uint64_t> *a64 )
{
    return a64->is_lock_free();
}

这是isLockFreeAtomic的反汇编

|?isLockFreeAtomic@@YA_NPAU?$atomic@_K@std@@@Z| PROC
    movs        r0,#1
    bx          lr
ENDP

这就是returns true,又名1

该实现选择使用,alignof( atomic<int64_t> ) == 8以便每个atomic<int64_t>都正确对齐。这避免了对每个加载和存储进行运行时对齐检查的需要。

(编者注:这很常见;大多数现实生活中的C ++实现std::is_always_lock_free都是以这种方式工作的。这就是为什么如此有用:因为通常对于类型为true的类型is_lock_free()都是正确的。)


1
是的,大多数实现都选择提供atomic<uint64_t>alignof() == 8因此它们不必在运行时检查对齐方式。这个旧的API给他们提供了不这样做的选项,但是在当前的HW中,仅要求对齐(否则UB,例如非原子性)就更有意义了。即使在int64_t可能只有4字节对齐的32位代码中,也atomic<int64_t>需要8字节。请参阅我对另一个答案的评论
Peter Cordes

简而言之:如果编译器选择使alignof基本类型的值与硬件的“良好”对齐相同,那么 is_lock_free它将始终是true(也是is_always_lock_free)。您的编译器正是在执行此操作。但是该API存在,因此其他编译器可以做不同的事情。
Max Langhof

1
您可以肯定地确定,如果该语言指出未对齐访问具有未定义的行为,则所有原子必须正确对齐。因此,任何实现都不会执行任何运行时检查。
Bonita Montero,

@BonitaMontero是的,但是没有什么语言禁止alignof(std::atomic<double>) == 1(因此,即使硬件只能保证double4或4上s 的无锁原子操作,C ++也不会有“未对齐的访问”,因此没有UB)。8个字节边界。然后,编译器将不得不在未对齐的情况下使用锁(并is_lock_free根据对象实例的存储位置从中返回适当的布尔值)。
Max Langhof
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.