简体   繁体   English

标准::原子<bool> ARM 上的无锁不一致(树莓派 3)

[英]std::atomic<bool> lock-free inconsistency on ARM (raspberry pi 3)

I had a problem with a static assert.我有一个静态断言的问题。 The static assert was exactly like this:静态断言完全是这样的:

static_assert(std::atomic<bool>::is_always_lock_free);

and the code failed on Raspberry Pi 3 (Linux raspberrypi 4.19.118-v7+ #1311 SMP Mon Apr 27 14:21:24 BST 2020 armv7l GNU/Linux).并且代码在 Raspberry Pi 3 上失败(Linux raspberrypi 4.19.118-v7+ #1311 SMP Mon Apr 27 14:21:24 BST 2020 armv7l GNU/Linux)。

On the cppreference.com atomic::is_always_lock_free reference site it is stated that:cppreference.com atomic::is_always_lock_free 参考站点上指出:

Equals true if this atomic type is always lock-free and false if it is never or sometimes lock-free.如果此原子类型始终是无锁的,则等于 true,如果它从不或有时是无锁的,则等于 false。 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.该常量的值与宏 ATOMIC_xxx_LOCK_FREE 的定义、成员函数 is_lock_free 和非成员函数 std::atomic_is_lock_free 一致。

The first strange thing for me is "sometimes lock-free".对我来说,第一个奇怪的事情是“有时无锁”。 What does it depend on?它取决于什么? But questions later, back to the problem.不过后面的问题,回到问题上来。

I made a little test.我做了一个小测试。 Wrote this code:写了这段代码:

#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;
}

compiled and ran it on raspberry using g++ -std=c++17 atomic_test.cpp && ./a.out (g++ 7.3.0 and 8.3.0, but that shouldn't matter) and got:使用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

As you can see it is not as consistent as stated on the cppreference site... For comparison I ran it on my laptop (Ubuntu 18.04.5) with g++ 7.5.0 and got:正如您所看到的,它不像 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

So there is a difference in ATOMIC_BOOL_LOCK_FREE 's value and of course the is_always_lock_free constant.所以ATOMIC_BOOL_LOCK_FREE的值和is_always_lock_free常量是有区别的。 Looking for the definition of ATOMIC_BOOL_LOCK_FREE all I could find is寻找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;

What is the difference between ATOMIC_BOOL_LOCK_FREE (or __GCC_ATOMIC_BOOL_LOCK_FREE ) being equal to 1 or 2? ATOMIC_BOOL_LOCK_FREE (或__GCC_ATOMIC_BOOL_LOCK_FREE )等于 1 或 2 有什么区别? Is it a case where if 1 then it may or may not be lock-free and if 2 it is 100% lock-free?如果 1 那么它可能是或可能不是无锁的,如果 2 是 100% 无锁的,是否是这样? Are there any other values apart from 0?除了0还有其他值吗? Is this an error on the cppreference site where it is stated that all those return values should be consistent?这是 cppreference 站点上声明所有这些返回值应该一致的错误吗? Which of the results for the raspberry pi output is really true?树莓派输出的哪些结果是真的?

The ATOMIC_xxx_LOCK_FREE macros means:ATOMIC_xxx_LOCK_FREE宏表示:

  • 0​ for the built-in atomic types that are never lock-free 0​为内置的原子类型,可从来没有无锁
  • 1 for the built-in atomic types that are sometimes lock-free 1用于有时无锁的内置原子类型
  • 2 for the built-in atomic types that are always lock-free. 2对于始终无锁的内置原子类型。

So, in your PI environment, a std::atomic<bool> is sometimes lock-free and the dummy instance you are testing is lock-free - which means that all instances are.因此,在您的 PI 环境中, std::atomic<bool>有时是无锁的,而您正在测试的dummy实例是无锁的 -​​ 这意味着所有实例都是。

bool std::atomic_is_lock_free( const std::atomic<T>* obj ) : 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.在任何给定的程序执行中,无锁查询的结果对于相同类型的所有指针都是相同的。

The only downside is that you don't know if the type is lock-free until you run the program.唯一的缺点是,在运行程序之前,您不知道该类型是否是无锁的。

If(not std::atomic_is_lock_free(&dummy)) {
    std::cout << "Sorry, the program will be slower than expected\n";
}

1 means "sometimes lock free" in the standard. 1在标准中表示“有时无锁”。 But really that means "not known to be lock free at compile time ".但实际上这意味着“在编译时不知道是无锁的”。

Without compiler options, GCC's default baseline includes ARM chips so old that they don't support the necessary instructions for atomic RMWs, so it has to make code that could run on ancient CPUs, always calling libatomic functions instead of inlining atomic operations.如果没有编译器选项,GCC 的默认基线包括很旧的 ARM 芯片,以至于它们不支持原子 RMW 的必要指令,因此它必须编写可以在古老的 CPU 上运行的代码,总是调用 libatomic 函数而不是内联原子操作。

The runtime query function returns true when you run it on an RPi with its ARMv7 or ARMv8 CPU.当您在带有 ARMv7 或 ARMv8 CPU 的 RPi 上运行运行时查询函数时,它会返回 true。

With -march=native or -mcpu=cortex-a53 you'd get is_always_lock_free being true, because it's known at compile time that the target machine definitely supports the required instructions.使用-march=native-mcpu=cortex-a53你会得到is_always_lock_free是真的,因为在编译时就知道目标机器肯定支持所需的指令。 (Those options tell GCC to make a binary that might not run on other / older CPUs.) This was confirmed by the OP in comments . (这些选项告诉 GCC 制作一个可能无法在其他/较旧 CPU 上运行的二进制文件。) OP 在评论中证实了这一点。

Without that compile option, std::atomic operations have to call libatomic functions, so there's extra overhead even on a modern CPU.如果没有那个编译选项, std::atomic操作必须调用 libatomic 函数,所以即使在现代 CPU 上也会有额外的开销。

The way GCC (and all sane compilers) implement std::atomic<T> , it's either lock free for all instances or none, not checking alignment or whatever at runtime per object. GCC(和所有健全的编译器)实现std::atomic<T> ,它要么对所有实例无锁,要么没有,不检查对齐或每个对象在运行时的任何内容。

alignof( std::atomic<int64_t> ) is 8 even if alignof( int64_t ) was only 4 on a 32-bit machine, so it's undefined behaviour if you have a misaligned atomic object. alignof( std::atomic<int64_t> )是 8,即使alignof( int64_t )在 32 位机器上只有 4,所以如果你有一个未对齐的原子对象,它是未定义的行为。 (The practical symptoms of that UB could include tearing, ie non-atomicity, for pure-load and pure-store.) If you follow C++ rules, all your atomic objects will be aligned; (对于纯加载和纯存储,UB 的实际症状可能包括撕裂,即非原子性。)如果您遵循 C++ 规则,您的所有原子对象都将对齐; you'd only have a problem if you cast a misaligned pointer to atomic<int64_t> * and tried to use it.如果您将未对齐的指针投射到atomic<int64_t> *并尝试使用它,您只会遇到问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM