问题描述
我对静态断言有疑问。静态断言就是这样的:
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 reference网站上指出:
如果此原子类型始终是无锁的,则等于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
(g ++ 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网站上所说的不一致...为了进行比较,我在装有g ++ 7.5.0的笔记本电脑(Ubuntu 18.04.5)上运行了它,并得到了:
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站点上的错误消息,指出所有这些返回值都应该一致吗?树莓派输出的哪个结果是真的?
解决方法
ATOMIC_xxx_LOCK_FREE
宏的意思是:
-
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上运行的二进制文件。)这是confirmed by the OP in comments。
没有该编译选项,std::atomic
操作必须调用libatomic函数,因此即使在现代CPU上也有额外的开销。
GCC(和所有明智的编译器)实现std::atomic<T>
的方式,它对于所有实例都是无锁的,或者没有锁,不检查对象的对齐方式或在运行时执行任何操作。
alignof( std::atomic<int64_t> )
仅为4, alignof( int64_t )
也为8,因此如果原子对象未对齐,则它是未定义的行为。 (该UB的实际症状可能包括撕裂,即纯负载和纯存储的非原子性。)如果遵循C ++规则,则所有原子对象都将对齐;仅当您将未对齐的指针投射到atomic<int64_t> *
并尝试使用它时,您才会遇到问题。