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

Rog*_*gus 6 c++ gcc arm raspberry-pi stdatomic

我的静态断言有问题。静态断言完全是这样的:

static_assert(std::atomic<bool>::is_always_lock_free);
Run Code Online (Sandbox Code Playgroud)

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

cppreference.comatomic::is_always_lock_free 参考站点上指出:

如果此原子类型始终是无锁的,则等于 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;
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,它与 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
Run Code Online (Sandbox Code Playgroud)

ATOMIC_BOOL_LOCK_FREE所以的值和常数当然是有区别的is_always_lock_freeATOMIC_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;
Run Code Online (Sandbox Code Playgroud)

ATOMIC_BOOL_LOCK_FREE(或__GCC_ATOMIC_BOOL_LOCK_FREE) 等于 1 或 2有什么区别?是否存在这样的情况:如果是 1 那么它可能是无锁的,也可能不是无锁的,如果是 2 则它是 100% 无锁的?除了0还有其他值吗?这是 cppreference 网站上的一个错误,其中指出所有这些返回值应该一致吗?树莓派输出的哪个结果是真实的?

Ted*_*gmo 6

ATOMIC_xxx_LOCK_FREE的意思是:

\n
    \n
  • 0\xe2\x80\x8b对于永远不会无锁的内置原子类型
  • \n
  • 1对于有时是无锁的内置原子类型
  • \n
  • 2对于始终无锁的内置原子类型。
  • \n
\n

因此,在您的 PI 环境中,astd::atomic<bool>有时是无锁的,并且dummy您正在测试的实例无锁的 - 这意味着所有实例都是无锁的。

\n

bool std::atomic_is_lock_free( const std::atomic<T>* obj ):

\n
\n

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

\n
\n

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

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

  • 最有可能的是,“有时无锁”意味着“在*编译时*未知是否无锁”,但是一旦您在具有 ARMv7 CPU 的 RPi 上运行它,运行时查询函数就会返回 true。可能使用“-march=native”或“-mcpu=cortex-whatever”,你会得到“is_always_lock_free”为“true”。(如果没有该编译选项,std::atomic 操作必须调用“libatomic”函数,因此即使在现代 CPU 上也会产生额外的开销)。GCC(和所有理智的编译器)实现“std::atomic&lt;T&gt;”的方式,它要么对所有实例都是无锁的,要么没有锁,在每个实例运行时不检查对齐或其他任何事情。 (4认同)
  • 我将 `-mcpu=cortex-a53` 添加到编译中,它起作用了!`ATOMIC_BOOL_LOCK_FREE` 为 2 并且 `std::atomic&lt;bool&gt;::is_always_lock_free` 计算结果为 `true`!非常感谢你的帮助! (3认同)
  • PeterCordes:这似乎很合理。@Rogus你可以尝试“-m”选项来看看你是否让它始终无锁? (2认同)

Pet*_*des 5

1在标准中表示“有时无锁”。 但实际上这意味着“在编译时不知道是否是无锁的”。

如果没有编译器选项,GCC 的默认基线包括非常旧的 ARM 芯片,以至于它们不支持原子 RMW 的必要指令,因此它必须编写可以在古老的 CPU 上运行的代码,始终调用 libatomic 函数而不是内联原子操作。

当您在具有 ARMv7 或 ARMv8 CPU 的 RPi 上运行时,运行时查询函数将返回 true。

有了-march=nativeor-mcpu=cortex-a53你就会得到真实的,因为在编译时is_always_lock_free就知道目标机器肯定支持所需的指令。(这些选项告诉 GCC 制作一个可能无法在其他/较旧的 CPU 上运行的二进制文件。)OP 在注释中证实了这一点。

如果没有该编译选项,std::atomic操作必须调用 libatomic 函数,因此即使在现代 CPU 上也会产生额外的开销。

GCC(和所有理智的编译器)实现的方式std::atomic<T>,它要么对所有实例都是无锁的,要么没有锁,不检查对齐或在运行时每个对象的任何内容。

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