[英]std::atomic<bool> lock-free inconsistency on ARM (raspberry pi 3)
我有一个静态断言的问题。 静态断言完全是这样的:
static_assert(std::atomic<bool>::is_always_lock_free);
并且代码在 Raspberry Pi 3 上失败(Linux raspberrypi 4.19.118-v7+ #1311 SMP Mon Apr 27 14:21:24 BST 2020 armv7l GNU/Linux)。
在cppreference.com atomic::is_always_lock_free 参考站点上指出:
如果此原子类型始终是无锁的,则等于 true,如果它从不或有时是无锁的,则等于 false。 该常量的值与宏 ATOMIC_xxx_LOCK_FREE 的定义、成员函数 is_lock_free 和非成员函数 std::atomic_is_lock_free 一致。
对我来说,第一个奇怪的事情是“有时无锁”。 它取决于什么? 不过后面的问题,回到问题上来。
我做了一个小测试。 写了这段代码:
#include <iostream>
#include <atomic>
int main()
{
std::atomic<bool> dummy {};
std::cout << std::boolalpha
<< "ATOMIC_BOOL_LOCK_FREE --> " << ATOMIC_BOOL_LOCK_FREE << std::endl
<< "dummy.is_lock_free() --> " << dummy.is_lock_free() << std::endl
<< "std::atomic_is_lock_free(&dummy) --> " << std::atomic_is_lock_free(&dummy) << std::endl
<< "std::atomic<bool>::is_always_lock_free --> " << std::atomic<bool>::is_always_lock_free << std::endl;
return 0;
}
使用g++ -std=c++17 atomic_test.cpp && ./a.out
7.3.0 和 8.3.0,但这应该无关紧要)在树莓上编译并运行它并得到:
ATOMIC_BOOL_LOCK_FREE --> 1
dummy.is_lock_free() --> true
std::atomic_is_lock_free(&dummy) --> true
std::atomic<bool>::is_always_lock_free --> false
正如您所看到的,它不像 cppreference 站点上所说的那样一致......为了进行比较,我在我的笔记本电脑(Ubuntu 18.04.5)上使用 g++ 7.5.0 运行它并得到:
ATOMIC_BOOL_LOCK_FREE --> 2
dummy.is_lock_free() --> true
std::atomic_is_lock_free(&dummy) --> true
std::atomic<bool>::is_always_lock_free --> true
所以ATOMIC_BOOL_LOCK_FREE
的值和is_always_lock_free
常量是有区别的。 寻找ATOMIC_BOOL_LOCK_FREE
的定义,我能找到的是
c++/8/bits/atomic_lockfree_defines.h: #define ATOMIC_BOOL_LOCK_FREE __GCC_ATOMIC_BOOL_LOCK_FREE
c++/8/atomic: static constexpr bool is_always_lock_free = ATOMIC_BOOL_LOCK_FREE == 2;
ATOMIC_BOOL_LOCK_FREE
(或__GCC_ATOMIC_BOOL_LOCK_FREE
)等于 1 或 2 有什么区别? 如果 1 那么它可能是或可能不是无锁的,如果 2 是 100% 无锁的,是否是这样? 除了0还有其他值吗? 这是 cppreference 站点上声明所有这些返回值应该一致的错误吗? 树莓派输出的哪些结果是真的?
0
为内置的原子类型,可从来没有无锁1
用于有时无锁的内置原子类型2
对于始终无锁的内置原子类型。 因此,在您的 PI 环境中, std::atomic<bool>
有时是无锁的,而您正在测试的dummy
实例是无锁的 - 这意味着所有实例都是。
bool std::atomic_is_lock_free( const std::atomic<T>* obj )
:
在任何给定的程序执行中,无锁查询的结果对于相同类型的所有指针都是相同的。
唯一的缺点是,在运行程序之前,您不知道该类型是否是无锁的。
If(not std::atomic_is_lock_free(&dummy)) {
std::cout << "Sorry, the program will be slower than expected\n";
}
1
在标准中表示“有时无锁”。 但实际上这意味着“在编译时不知道是无锁的”。
如果没有编译器选项,GCC 的默认基线包括很旧的 ARM 芯片,以至于它们不支持原子 RMW 的必要指令,因此它必须编写可以在古老的 CPU 上运行的代码,总是调用 libatomic 函数而不是内联原子操作。
当您在带有 ARMv7 或 ARMv8 CPU 的 RPi 上运行运行时查询函数时,它会返回 true。
使用-march=native
或-mcpu=cortex-a53
你会得到is_always_lock_free
是真的,因为在编译时就知道目标机器肯定支持所需的指令。 (这些选项告诉 GCC 制作一个可能无法在其他/较旧 CPU 上运行的二进制文件。) OP 在评论中证实了这一点。
如果没有那个编译选项, std::atomic
操作必须调用 libatomic 函数,所以即使在现代 CPU 上也会有额外的开销。
GCC(和所有健全的编译器)实现std::atomic<T>
,它要么对所有实例无锁,要么没有,不检查对齐或每个对象在运行时的任何内容。
alignof( std::atomic<int64_t> )
是 8,即使alignof( int64_t )
在 32 位机器上只有 4,所以如果你有一个未对齐的原子对象,它是未定义的行为。 (对于纯加载和纯存储,UB 的实际症状可能包括撕裂,即非原子性。)如果您遵循 C++ 规则,您的所有原子对象都将对齐; 如果您将未对齐的指针投射到atomic<int64_t> *
并尝试使用它,您只会遇到问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.