簡體   English   中英

C ++ 11中的Double-Checked Lock Singleton

[英]Double-Checked Lock Singleton in C++11

以下單例實現數據 - 競爭是免費的嗎?

static std::atomic<Tp *> m_instance;
...

static Tp &
instance()
{
    if (!m_instance.load(std::memory_order_relaxed))
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (!m_instance.load(std::memory_order_acquire))
        {
            Tp * i = new Tp;
            m_instance.store(i, std::memory_order_release);    
        }    
    }

    return * m_instance.load(std::memory_order_relaxed);
}

加載操作的std::memory_model_acquire是多余的嗎? 是否可以通過將它們切換到std::memory_order_relaxed來進一步放寬加載和存儲操作? 在這種情況下, std::mutex的獲取/釋放語義是否足以保證其正確性,或者還需要進一步的std::atomic_thread_fence(std::memory_order_release)來確保對構造函數的內存的寫入發生在放松的商店? 然而,使用fence相當於使用memory_order_release存儲?

編輯 :感謝John的回答,我提出了以下應該是數據競爭的實現。 盡管內部負載可能完全是非原子的,但我決定放棄一個寬松的負載,因為它不會影響性能。 與總是具有獲取存儲器順序的外部負載相比,thread_local機器提高了訪問大約一個數量級的實例的性能。

static Tp &
instance()
{
    static thread_local Tp *instance;

    if (!instance && 
        !(instance = m_instance.load(std::memory_order_acquire)))
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (!(instance = m_instance.load(std::memory_order_relaxed)))
        {
            instance = new Tp; 
            m_instance.store(instance, std::memory_order_release);    
        }    
    }
    return *instance;
}

我認為這是一個很好的問題,John Calsbeek有正確的答案。

然而,要明確一個懶惰的單身人士最好使用經典的邁耶斯單身人士。 它在C ++ 11中保證了正確的語義。

§6.7.4

...如果控件在初始化變量時同時進入聲明,則並發執行應等待初始化完成。 ...

Meyer的單例是首選,因為編譯器可以積極地優化並發代碼。 如果必須保留std::mutex的語義,編譯器會受到更多限制。 此外,邁耶的單身是2行 ,幾乎不可能出錯。

這是邁耶的單身人士的典型例子。 簡單,優雅,在c ++ 03中破碎。 但在c ++ 11中簡單,優雅,強大。

class Foo
{
public:
   static Foo& instance( void )
   {
      static Foo s_instance;
      return s_instance;
   }
};

該實施不是無競爭的。 單例的原子存儲雖然使用了釋放語義,但只會與匹配的獲取操作同步 - 即,已經由互斥鎖保護的加載操作。

在鎖定線程完成初始化單例之前,外部寬松加載可能會讀取非空指針。

另一方面,由鎖保護的獲取是多余的。 它將與另一個線程上具有發布語義的任何商店同步,但此時(由於互斥鎖),可能存儲的唯一線程是當前線程。 這個負載甚至不需要是原子的 - 沒有商​​店可以從另一個線程發生。

請參閱Anthony Williams關於C ++ 0x多線程的系列文章

另請參見call_once 如果您之前使用單例執行某些操作,但實際上並未將返回的對象用於任何操作,則call_once可能是更好的解決方案。 對於常規單例,你可以做call_once來設置一個(全局?)變量,然后返回那個變量......

為簡潔起見簡化:

template< class Function, class... Args>
void call_once( std::once_flag& flag, Function&& f, Args&& args...);
  • 正好執行一個函數的執行,作為f傳遞給組中的調用(相同的標志對象)

  • 在成功完成上述所選功能的執行之前,組中沒有調用返回

暫無
暫無

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

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