std::atomic<bool> ARM 上的无锁不一致 (raspberry pi 3)
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 reference 网站上声明:
Equals true if this atomic type is always lock-free and false if it is never or sometimes lock-free.
The value of this constant is consistent with both the macro ATOMIC_xxx_LOCK_FREE, where defined, with the member function is_lock_free and non-member function std::atomic_is_lock_free.
对我来说第一个运行ge 事情是“有时是无锁的”。它取决于什么?不过后面的问题,回到问题。
我做了个小测试。写了这段代码:
#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 网站上所说的不一致...为了比较,我 运行 在我的笔记本电脑 (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 站点上的一个错误吗,该站点声明所有这些 return 值应该一致? raspberry pi 输出的哪个结果是真实的?
ATOMIC_xxx_LOCK_FREE
宏表示:
0
对于永远不会是 lock-free 的 built-in 原子类型
1
对于 built-in 原子类型有时是 lock-free
2
对于总是 lock-free. 的 built-in 原子类型
因此,在您的 PI 环境中,std::atomic<bool>
有时是 lock-free 而您正在测试的 dummy
实例 是 lock-free - 这意味着 all 个实例是。
bool std::atomic_is_lock_free( const std::atomic<T>* obj )
:
In any given program execution, the result of the lock-free query is the same for all pointers of the same type.
唯一的缺点是你不知道类型是否是 lock-free,直到你 运行 程序。
If(not std::atomic_is_lock_free(&dummy)) {
std::cout << "Sorry, the program will be slower than expected\n";
}
1
在标准中表示“有时无锁”。 但这实际上意味着“不知道在编译时是无锁的”。
没有编译器选项,GCC 的默认基线包括 ARM 芯片,它们太旧以至于它们不支持原子 RMW 的必要指令,因此它必须编写可以 运行 在古老的 CPU 上的代码s,总是调用 libatomic 函数而不是内联原子操作。
运行time query function returns true when you 运行 it on an RPi with its ARMv7 or ARMv8 CPU.
使用 -march=native
或 -mcpu=cortex-a53
你会得到 is_always_lock_free
为真,因为在编译时 已知 目标机器肯定支持所需的指令。 (这些选项告诉 GCC 制作一个二进制文件,该二进制文件可能不会 运行 在其他/较旧的 CPU 上。)这是 。
没有那个编译选项,std::atomic
操作必须调用 libatomic 函数,所以即使在现代 CPU.
上也会有额外的开销
GCC(和所有正常的编译器)实现 std::atomic<T>
的方式,它要么对所有实例都是无锁的,要么 none,不检查对齐或每个对象 运行 时间的任何内容。
alignof( std::atomic<int64_t> )
是 8 即使 alignof( int64_t )
在 32 位机器上只有 4,所以如果你有一个未对齐的原子对象,它是未定义的行为。 (该 UB 的实际症状可能包括撕裂,即 non-atomicity,对于 pure-load 和 pure-store。)如果您遵循 C++ 规则,您的所有原子对象都将对齐;如果您将未对齐的指针投射到 atomic<int64_t> *
并尝试使用它,您只会遇到问题。
我遇到了静态断言的问题。静态断言完全是这样的:
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 网站上声明:
Equals true if this atomic type is always lock-free and false if it is never or sometimes lock-free. The value of this constant is consistent with both the macro ATOMIC_xxx_LOCK_FREE, where defined, with the member function is_lock_free and non-member function std::atomic_is_lock_free.
对我来说第一个运行ge 事情是“有时是无锁的”。它取决于什么?不过后面的问题,回到问题。
我做了个小测试。写了这段代码:
#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 网站上所说的不一致...为了比较,我 运行 在我的笔记本电脑 (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 站点上的一个错误吗,该站点声明所有这些 return 值应该一致? raspberry pi 输出的哪个结果是真实的?
ATOMIC_xxx_LOCK_FREE
宏表示:
0
对于永远不会是 lock-free 的 built-in 原子类型
1
对于 built-in 原子类型有时是 lock-free2
对于总是 lock-free. 的 built-in 原子类型
因此,在您的 PI 环境中,std::atomic<bool>
有时是 lock-free 而您正在测试的 dummy
实例 是 lock-free - 这意味着 all 个实例是。
bool std::atomic_is_lock_free( const std::atomic<T>* obj )
:
In any given program execution, the result of the lock-free query is the same for all pointers of the same type.
唯一的缺点是你不知道类型是否是 lock-free,直到你 运行 程序。
If(not std::atomic_is_lock_free(&dummy)) {
std::cout << "Sorry, the program will be slower than expected\n";
}
1
在标准中表示“有时无锁”。 但这实际上意味着“不知道在编译时是无锁的”。
没有编译器选项,GCC 的默认基线包括 ARM 芯片,它们太旧以至于它们不支持原子 RMW 的必要指令,因此它必须编写可以 运行 在古老的 CPU 上的代码s,总是调用 libatomic 函数而不是内联原子操作。
运行time query function returns true when you 运行 it on an RPi with its ARMv7 or ARMv8 CPU.
使用 -march=native
或 -mcpu=cortex-a53
你会得到 is_always_lock_free
为真,因为在编译时 已知 目标机器肯定支持所需的指令。 (这些选项告诉 GCC 制作一个二进制文件,该二进制文件可能不会 运行 在其他/较旧的 CPU 上。)这是
没有那个编译选项,std::atomic
操作必须调用 libatomic 函数,所以即使在现代 CPU.
GCC(和所有正常的编译器)实现 std::atomic<T>
的方式,它要么对所有实例都是无锁的,要么 none,不检查对齐或每个对象 运行 时间的任何内容。
alignof( std::atomic<int64_t> )
是 8 即使 alignof( int64_t )
在 32 位机器上只有 4,所以如果你有一个未对齐的原子对象,它是未定义的行为。 (该 UB 的实际症状可能包括撕裂,即 non-atomicity,对于 pure-load 和 pure-store。)如果您遵循 C++ 规则,您的所有原子对象都将对齐;如果您将未对齐的指针投射到 atomic<int64_t> *
并尝试使用它,您只会遇到问题。