std::atomic<bool>ARM 上的无锁不一致(树莓派 3) [英] std::atomic&lt;bool&gt; lock-free inconsistency on ARM (raspberry pi 3)

查看:27
本文介绍了std::atomic<bool>ARM 上的无锁不一致(树莓派 3)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在使用静态断言时遇到了问题.静态断言完全是这样的:

static_assert(std::atomic::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 参考 网站上声明如下:

<块引用>

如果这个原子类型总是无锁则等于真,如果它从不或有时是无锁的则等于假.该常量的值与宏 ATOMIC_xxx_LOCK_FREE 的定义一致,与成员函数 is_lock_free 和非成员函数 std::atomic_is_lock_free 一致.

对我来说第一个奇怪的事情是有时无锁".它取决于什么?但问题过后,回到问题.

我做了一个小测试.写了这段代码:

#include #include <atomic>int main(){std::atomic假的 {};std::cout <<标准::布尔阿尔法<<"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;返回0;}

使用 g++ -std=c++17 atomic_test.cpp && 在树莓上编译并运行它./a.out(g++ 7.3.0 和 8.3.0,但这应该无关紧要)并得到:

ATOMIC_BOOL_LOCK_FREE -->1dummy.is_lock_free() -->真的std::atomic_is_lock_free(&dummy) -->真的std::atomic::is_always_lock_free -->错误的

正如你所看到的,它不像 cppreference 站点上所说的那样一致......为了进行比较,我在我的笔记本电脑(Ubuntu 18.04.5)和 g++ 7.5.0 上运行它并得到:

ATOMIC_BOOL_LOCK_FREE -->2dummy.is_lock_free() -->真的std::atomic_is_lock_free(&dummy) -->真的std::atomic::is_always_lock_free -->真的

所以 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_FREEc++/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 站点上声明所有这些返回值应该一致的错误吗?树莓派输出的哪些结果是真的?

解决方案

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 的方式,它要么对所有实例都是无锁的,要么没有,在每个对象的运行时不检查对齐或其他任何事情.>

alignof( std::atomic<int64_t> ) 是 8,即使 alignof( int64_t ) 在 32 位机器上只有 4,所以如果你有一个未对齐的原子对象.(对于纯加载和纯存储,UB 的实际症状可能包括撕裂,即非原子性.)如果您遵循 C++ 规则,您的所有原子对象都将对齐;如果您将未对齐的指针投射到 atomic<int64_t>,您只会遇到问题.* 并尝试使用它.

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).

On the cppreference.com atomic::is_always_lock_free reference site it is stated that:

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.

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:

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:

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. Looking for the definition of ATOMIC_BOOL_LOCK_FREE all I could find is

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? Is it a case where if 1 then it may or may not be lock-free and if 2 it is 100% lock-free? Are there any other values apart from 0? Is this an error on the cppreference site where it is stated that all those return values should be consistent? Which of the results for the raspberry pi output is really true?

解决方案

1 means "sometimes lock free" in the standard. 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.

The runtime query function returns true when you run it on an RPi with its ARMv7 or ARMv8 CPU.

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. (Those options tell GCC to make a binary that might not run on other / older CPUs.) This was confirmed by the OP in comments.

Without that compile option, std::atomic operations have to call libatomic functions, so there's extra overhead even on a modern 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.

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. (The practical symptoms of that UB could include tearing, i.e. non-atomicity, for pure-load and pure-store.) If you follow C++ rules, all your atomic objects will be aligned; you'd only have a problem if you cast a misaligned pointer to atomic<int64_t> * and tried to use it.

这篇关于std::atomic<bool>ARM 上的无锁不一致(树莓派 3)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆