ARM 上的 std::atomic<bool> 无锁不一致(树莓派 3)

Posted

技术标签:

【中文标题】ARM 上的 std::atomic<bool> 无锁不一致(树莓派 3)【英文标题】:std::atomic<bool> lock-free inconsistency on ARM (raspberry pi 3) 【发布时间】:2020-10-07 10:23:11 【问题描述】:

我遇到了静态断言的问题。静态断言是这样的:

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 &amp;&amp; ./a.out(g++ 7.3.0 和 8.3.0,但这不重要)在 raspberry 上编译并运行它并得到:

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 站点上的一个错误,其中声明所有这些返回值都应该是一致的?树莓派输出的哪个结果是真的?

【问题讨论】:

【参考方案1】:

ATOMIC_xxx_LOCK_FREE 宏表示:

0​ 用于永远不会无锁的内置原子类型 1 用于有时是无锁的内置原子类型 2 用于始终无锁的内置原子类型。

因此,在您的 PI 环境中,std::atomic&lt;bool&gt; 有时是无锁的,而您正在测试的 dummy 实例无锁的 - 这意味着 所有 实例是。

bool std::atomic_is_lock_free( const std::atomic&lt;T&gt;* obj ):

在任何给定的程序执行中,无锁查询的结果对于相同类型的所有指针都是相同的。

唯一的缺点是在你运行程序之前你不知道类型是否是无锁的。

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

【讨论】:

最有可能的是,“有时无锁”的意思是“在编译时不知道是无锁的”,但运行时查询函数一旦在 RPi 上运行就会返回 true凭借其 ARMv7 CPU。可能使用-march=native-mcpu=cortex-whatever 你会得到is_always_lock_free 是true。 (没有那个编译选项,std::atomic 操作必须调用 libatomic 函数,所以即使在现代 CPU 上也会有额外的开销)。 GCC(和所有健全的编译器)实现std::atomic&lt;T&gt; 的方式,它要么对所有实例无锁,要么无锁,不检查对齐或在每个实例的运行时进行任何操作。 PeterCordes:这似乎是合理的。 @Rogus 你可以试试-m 选项,看看你是否让它始终无锁? 我在编译中添加了-mcpu=cortex-a53,它成功了! ATOMIC_BOOL_LOCK_FREE 是 2 并且 std::atomic&lt;bool&gt;::is_always_lock_free 评估为 true!非常感谢您的帮助!【参考方案2】:

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&lt;T&gt; 的方式,它要么对所有实例无锁,要么无锁,不检查对齐或在运行时每个对象的任何内容。

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

【讨论】:

为 RISC-V 编译时会怎样? @AaronFranke:如果 LL/SC 指令是 RISC-V 的基线,则 IDK。如果是这样,那么即使没有任何拱形选项,is_always_lock_free 也是正确的。您可以随时使用 RISC-V GCC 或 clang 在 godbolt.org 上试用它。

以上是关于ARM 上的 std::atomic<bool> 无锁不一致(树莓派 3)的主要内容,如果未能解决你的问题,请参考以下文章

std::atomic<std::string> 是不是正常工作?

原子读取然后用 std::atomic 写入

std::atomic_flag 停止多个线程

C++ error: use of deleted function ‘std::atomic<short unsigned int>::atomic(const std::atomic<short

在 Windows 中等待 std::atomic<int> 的正确方法?

未解析的外部符号“std::atomic_fetch_add”