繁体   English   中英

标准::原子<bool> ARM 上的无锁不一致(树莓派 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 参考站点上指出:

如果此原子类型始终是无锁的,则等于 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 && ./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

正如您所看到的,它不像 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 站点上声明所有这些返回值应该一致的错误吗? 树莓派输出的哪些结果是真的?

ATOMIC_xxx_LOCK_FREE宏表示:

  • 0​为内置的原子类型,可从来没有无锁
  • 1用于有时无锁的内置原子类型
  • 2对于始终无锁的内置原子类型。

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

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

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

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

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

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

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

暂无
暂无

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

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