據此, 64 位加載/存盤被認為是 arm64 上的原子訪問。鑒于此,以下程式在為 arm64 編譯時是否仍被認為存在資料競爭(因此可能出現 UB)(忽略其他記憶體訪問的順序)
uint64_t x;
// Thread 1
void f()
{
uint64_t a = x;
}
// Thread 2
void g()
{
x = 1;
}
相反,如果我將其切換為使用
std::atomic<uint64_t> x{};
// Thread 1
void f()
{
uint64_t a = x.load(std::memory_order_relaxed);
}
// Thread 2
void g()
{
x.store(1, std::memory_order_relaxed);
}
第二個程式是否被認為是免費的?
在 arm64 上,看起來編譯器最終會為普通的 64 位加載/存盤和原子的加載/存盤生成相同的指令memory_order_relaxed
,那么有什么區別?
uj5u.com熱心網友回復:
訪問是否是 C 語言標準意義上的資料競爭與底層硬體無關。該語言有自己的記憶體模型,即使直接編譯到目標架構不會出現問題,編譯器仍然可以基于程式沒有 C 記憶體模型意義上的資料競爭的假設進行優化.
在兩個執行緒中訪問非原子而不同步其中一個是寫入始終是 C 模型中的資料競爭。所以是的,第一個程式有資料競爭,因此有未定義的行為。
在第二個程式中,物件是原子的,因此不會存在資料競爭。
uj5u.com熱心網友回復:
std::atomic
解決4個問題。
一個是加載/存盤是原子的,這意味著您不會混合加載和存盤,例如,您從存盤之前加載 32 位,從存盤之后加載另一個 32 位。通常情況下,暫存器大小的所有內容在 CPU 本身的這個意義上自然是原子的。事情可能會因未對齊的訪問而中斷,可能只有當訪問跨越快取線時。在實作中,當 的大小超過 CPU 自行原子讀取/寫入的大小std::atmoic<T>
時,您將看到鎖的使用。T
另一件事std::atomic
是同步執行緒之間的訪問。僅僅因為一個執行緒將資料寫入變數并不意味著另一個執行緒會立即看到該資料。寫入 cpu 將資料放入它的存盤緩沖區,希望它再次被覆寫或相鄰的記憶體被寫入,并且可以組合 2 次寫入。一段時間后,資料進入 L1 快取,在那里它可以保留更長時間,然后是 L2 和 L3。根據架構,快取可能會或可能不會在 CPU 內核之間共享。它們也可能不會自動同步。因此,當您想從多個內核訪問相同的記憶體地址時,您必須告訴 CPU 與其他內核同步訪問。
第三件事與現代 CPU 進行亂序執行和推測執行有關。這意味著即使代碼檢查一個變數然后讀取第二個變數,CPU 也可能首先讀取第二個變數。如果第一個變數充當信號量,表示第二個變數已準備好被讀取,那么這可能會失敗,因為讀取發生在資料準備好之前。增加了阻止 CPU 進行這些重新排序的std::atomic
障礙,因此讀取和寫入在硬體中以特定順序發生。
第四件事與編譯器大致相同。std::atomic
防止編譯器在其中重新排序指令。或者將多個讀取或寫入優化為一個。
std::atomic
如果您只是使用它而不指定任何記憶體順序,那么所有這些都會為您自動完成。默認的記憶體順序是最強的順序。
但是當你使用
uint64_t a = x.load(std::memory_order_relaxed);
你告訴編譯器忽略大部分事情:
寬松的操作:對其他讀寫沒有同步或排序約束,只有這個操作的原子性得到保證
因此,您指示編譯器不要關心與其他執行緒或快取同步或保留指令的寫入順序。您所關心的只是讀取或寫入不會分成兩個或更多部分,您可以在其中獲得混合資料。將在另一個執行緒中load
獲取之前的store
全部資料或之后的全部資料。store
但是你得到的兩個值中的哪一個是完全不確定的。這是您免費獲得的所有 64 位加載/存盤,因此代碼是相同的。
注意:如果你有多個原子,那么訪問一個具有更強記憶體順序的原子將同步它們。因此,您可以看到以強順序執行一次加載的代碼以及以弱順序執行其他加載的代碼。寫入組也是如此。這可以加快訪問速度。但很難做到正確。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/496637.html
上一篇:通過discord.py從TkinterGUI發送Discord訊息
下一篇:為什么將實體變數分配給區域變數?