簡體   English   中英

讀取原子修改的值是否需要 memory 屏障?

[英]Is a memory barrier required to read a value that is atomically modified?

鑒於以下情況:

class Foo
{
public:
    void Increment()
    {
        _InterlockedIncrement(&m_value); // OSIncrementAtomic
    }

    long GetValue()
    {
        return m_value;
    }

private:
    long m_value;
};

讀取m_value是否需要 memory 屏障? 我的理解是_InterlockedIncrement將生成一個完整的 memory 屏障,並確保在任何后續加載發生之前存儲該值。 所以從這方面來看這聽起來很安全,但是, m_value可以緩存,即GetValue()是否可以返回一個過時的值,即使是在原子遞增時也是如此?

Jeff Preshing 的優秀文章供參考: https://preshing.com/20120515/memory-reordering-caught-in-the-act/

附加上下文:我正在關注有關無鎖編程的一系列文章,特別是在此處查看unfinishedJobs變量的用法和HasJobCompleted的潛在實現: https://blog.molecular-matters.com/2015 /08/24/job-system-2-0-lock-free-work-stealing-part-1-basics/

void Wait(const Job* job)
{
  // wait until the job has completed. in the meantime, work on any other job.
  while (!HasJobCompleted(job))
  {
    Job* nextJob = GetJob();
    if (nextJob)
    {
      Execute(nextJob);
    }
  }
}

可以通過將 unfinishedJobs 與 0 進行比較來確定作業是否已完成。

那么,在這種情況下, HasJobCompleted的可能實現是否需要 memory 屏障?

不,你不需要障礙,但如果讀者和作者在不同的線程中調用這些函數,你的代碼無論如何都會被破壞。 特別是如果讀者在循環中調用 read function。

TL:DR: 在增量中使用 C++11 std::atomic<long> m_valuereturn m_value++並在讀取器中return m_value 這將為您提供無數據爭用程序的順序一致性:執行將像線程以某種源順序交錯運行一樣工作。 (除非您違反規則並擁有其他非atomic共享數據。)您肯定希望從Increment返回一個值,如果您希望執行增量的線程永遠知道它們產生了什么值。 對於像int sequence_num = shared_counter++; count++; tmp = count; count++; tmp = count; .

如果您不需要在與讀取器/寫入器相同的線程中對其他對象的操作進行如此強的排序,則return m_value.load(std::memory_order_acquire)足以滿足大多數用途,而m_value.fetch_add(1, std::memory_order_acq_rel) 很少有程序實際上在任何地方都需要 StoreLoad 障礙; 即使使用acq_rel ,原子 RMW 實際上也不能重新排序。 (在 x86 上,它們的編譯結果與您使用seq_cst 。)

您不能在線程之間強制排序; 負載要么看到該值,要么不看到該值,具體取決於讀取線程是在獲取/嘗試獲取負載值之前還是之后看到來自編寫器的無效。 線程的全部意義在於它們不會彼此同步運行。


數據競賽 UB:

讀取m_value的循環可以將負載提升到循環之外,因為它不是atomic的(或者甚至是volatile作為 hack)。 這是數據競賽 UB,編譯器會破壞您的閱讀器。 看到這個多線程程序停留在優化模式但在 -O0 下正常運行

障礙不是這里的問題/解決方案,只是強制重新檢查 memory(或當前 CPU 看到的 memory 的緩存一致性視圖;像 L1d 和 L2 這樣的實際 CPU 緩存對此不是問題)。 這不是障礙的真正作用; 他們命令該線程訪問一致的緩存。 C++ 線程僅在具有一致緩存的內核上運行。

但是,如果沒有非常令人信服的理由,請認真對待自己的原子。 何時在多線程中使用 volatile? 幾乎從來沒有。 該答案解釋了緩存一致性,並且您不需要障礙來避免看到陳舊的值。

在許多現實世界的 C++ 實現中,像std::atomic_thread_fence()這樣的東西也將是一個“編譯器屏障”,它強制編譯器從 memory 重新加載甚至非原子變量,即使沒有volatile ,但這是一個實現細節。 所以它可能碰巧在某些 ISA 的某些編譯器上工作得很好。 而且對於編譯器發明的多重加載仍然不是完全安全的; 請參閱 LWN 文章誰害怕糟糕的優化編譯器? 有關詳細信息的示例; 主要針對 Linux kernel 如何使用volatile滾動自己的原子,這實際上由 GCC/clang 支持。


“最新價值”

初學者常常對此感到恐慌,並認為 RMW 操作在某種程度上更好,因為它們的指定方式。 由於它們是讀 + 寫綁定在一起的,並且每個memory位置都有一個修改順序,因此 RMW 操作必須等待對緩存行的寫訪問,這意味着在單個位置上序列化所有寫入和 RMW。

仍然保證(通過實際實現)原子變量的普通加載能夠迅速查看值。 (ISO C++建議在有限時間內及時查看值,但當然實際實現可以做得更好,因為它們運行在緩存一致的 CPU 硬件上。)

兩個線程之間沒有“立即”這樣的東西; 另一個線程中的加載看到存儲的值,或者它在存儲對其他線程可見但不可見之前運行。 通過線程調度等,總是有可能一個線程會加載一個值,但很長時間都不會使用它; 裝的時候是新鮮的。

所以這與正確性幾乎無關,剩下的就是擔心線程間延遲。 在某些情況下,障礙可能會有所幫助(減少后來 memory 操作的爭用,而不是更快地主動刷新你的商店,障礙只是等待它以正常方式發生)。 所以這通常是一個非常小的影響,而不是使用額外障礙的理由。

請參閱MESI 協議和 std::atomic - 它是否確保所有寫入立即對其他線程可見? . 並查看我對https://github.com/do.net/runtime/issues/67330#issuecomment-1083539281Does hardware memory barrier 除了提供必要的保證之外還能使原子操作的可見性更快嗎? 通常沒有,即使有,也不會太多。

如果你不需要為了正確性而排序,那么僅僅為了讓它比其他atomic變量更晚地查看這個atomic變量,當然不足以讓讀者放慢速度。 或者放慢作者的速度,讓它坐在那里什么也不做,也許讓它更快地完成一個 RFO 幾個周期,而不是完成其他有用的工作。

如果您對線程的使用在內核間延遲方面存在瓶頸,以至於值得這樣做,那么這可能表明您需要重新考慮您的設計。

沒有障礙或排序,只需std::atomicmemory_order_relaxed ,您通常仍會在 40 納秒內看到其他內核上的數據(在現代 x86 台式機/筆記本電腦上),就好像兩個線程都使用原子 RMW 一樣。 而且它不可能被延遲任何顯着的時間,比如一微秒,如果你為很多早期的商店創造了很多爭用,那么他們都需要很長時間才能提交,然后才能提交。 您絕對不必擔心長時間看不到商店。 (這當然只適用於具有volatileatomic或手動原子。普通的非易失性加載可能只在循環開始時檢查一次,然后再也不會檢查。這就是它們不能用於多線程的原因。)

是的,障礙應該在雙方:閱讀和寫作。 想象一下,您有一些寫緩沖區和一個加載隊列,其中所有內容都可能是無序的。 所以你在寫你的東西時刷新了寫緩沖區,但是你需要處理的加載隊列在另一個線程(處理器)上,它對你的刷新一無所知。 所以它總是一個成對的過程。

你也可以從編譯器的角度來考慮它:除非編譯器被迫序列化訪問,否則它可以自由地重新排序它可以安全地(根據它的觀點)做的任何事情。

也就是說,這完全是關於序列化而不是原子性。 這完全是另一回事。 您的寫作是原子的_InterlockedIncrement但閱讀不是原子的return m_value 所以這是一場比賽。

另外,我看到您的代碼需要原子性,但我沒有看到任何序列化的需要。 您不使用m_value保護任何東西。 至於“過時”的價值:通常你不能保證在某個時間點你不會有過時的價值,即使有障礙。 RMW 操作需要最新值,但其他操作不需要。 所以有一個障礙將有助於更快地獲得最新的價值,但僅此而已。 有了你的代碼而忘記了競爭,編譯器可能會安全地假設你沒有修改m_value並緩存它。 關於 CPU 也可以這樣說。


綜上所述:當您需要不受保護的變量時,只需使用std::atomic即可。 它將確保該值不被任何實體緩存。

簡短的回答:是的。

你的“閱讀”應該有“獲取”順序,所以當它在 incre.net 之后執行“釋放”時,你的Increment()結果將在另一個線程中可見。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM