[英]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 站點上聲明所有這些返回值應該一致的錯誤嗎? 樹莓派輸出的哪些結果是真的?
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.