簡體   English   中英

為什么 c++ singleton 需要 memory_order_acquire

[英]why c++ singleton need memory_order_acquire

Singleton* Singleton::getInstance() {
     Singleton* tmp = m_instance.load(std::memory_order_relaxed);
     std::atomic_thread_fence(std::memory_order_acquire);        //<--1
     if (tmp == nullptr) {
         std::lock_guard<std::mutex> lock(m_mutex);
         tmp = m_instance.load(std::memory_order_relaxed);
         if (tmp == nullptr) {
             tmp = new Singleton;
             assert(tmp != nullptr);    
             std::atomic_thread_fence(std::memory_order_release); //<--2
             m_instance.store(tmp, std::memory_order_relaxed);
         }
     }
     return tmp;
 }

這里有一個常見的 c++ singleton 實現, 2中有一個release fence (如上標記),很容易理解,它防止重新排序new Singleton ,如果沒有這個圍欄,另一個線程可能會在沒有執行構建的情況下獲得實例;

令我疑惑的是1中的acquire fencerelease fence承諾Singleton構造已經執行完然后存到m_instance,這里我們fetch instance的時候不執行構造就得不到instance,為什么還需要acquire fence 1 ?

而且,我們可以用 m_instance 操作內存順序替換atomic_thread_fence嗎,它們是一樣的嗎? (如下圖所示)

Singleton* Singleton::getInstance() {
     Singleton* tmp = m_instance.load(std::memory_order_acquire);
     if (tmp == nullptr) {
         std::lock_guard<std::mutex> lock(m_mutex);
         tmp = m_instance.load(std::memory_order_relaxed);
         if (tmp == nullptr) {
             tmp = new Singleton;
             assert(tmp != nullptr);    
             m_instance.store(tmp, std::memory_order_release);
         }
     }
     return tmp;
 }

是的,具有對m_instance操作的獲取/釋放順序的第二個版本是正確的,並且等同於第一個版本。 事實上,它甚至比第一個版本更可取,因為柵欄會影響所有前面的(獲取)/后續(釋放)原子操作,但您只需要同步m_instance上的操作。 這就是為什么在某些架構上顯式柵欄速度較慢的原因。


為什么首先需要獲取/釋放? 因為您需要在創建 singleton 和使用 singleton 之間建立先行關系以避免數據競爭。 假設如下:

  • 線程1調用Singleton::getInstance()並初始化; 這涉及創建一個新的 object 並將指針存儲在m_instance
  • 線程2調用Singleton::getInstance() ,觀察線程1寫入的指針

線程 2 最喜歡取消引用指針以訪問它指向的 object,並且此訪問很可能是非原子的。 因此,您有兩個對 object 的非原子訪問 - 一個在創建期間,一個在使用 object 時。如果這些不是按之前發生的關系排序,那么這就是數據競爭。

那么我們如何建立happens-before關系呢? 通過使用memory_order_release memory_order_acquire它。 當獲取加載操作觀察到發布存儲寫入的值時,加載與存儲同步,從而建立先行關系。 此外,object 的構造在存儲之前排序,並且加載在取消引用之前排序(之前排序也意味着發生之前),並且由於發生之前是可傳遞的,因此構造發生在取消引用之前。

有關 C++ memory model 的更多詳細信息,我推薦我與他人合着的這篇論文: Memory C/C++ 程序員模型

暫無
暫無

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

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