std :: atomic <bool> ARM树莓派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 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 && ./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

如您所见,它与cppreference网站上所说的不一致...为了进行比较,我在装有g ++ 7.5.0的笔记本电脑(Ubuntu 18.04.5)上运行了它,并得到了:

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上运行的二进制文件。)这是confirmed by the OP in comments

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

GCC(和所有明智的编译器)实现std::atomic<T>的方式,它对于所有实例都是无锁的,或者没有锁,不检查对象的对齐方式或在运行时执行任何操作。

即使在32位计算机上alignof( std::atomic<int64_t> )仅为4,

alignof( int64_t )也为8,因此如果原子对象未对齐,则它是未定义的行为。 (该UB的实际症状可能包括撕裂,即纯负载和纯存储的非原子性。)如果遵循C ++规则,则所有原子对象都将对齐;仅当您将未对齐的指针投射到atomic<int64_t> *并尝试使用它时,您才会遇到问题。