2014-04-02 54 views
1

我經常遇到這個線程安全結構的設計。如下面的版本1,一個線程可能很少會調用foo1::add_data(),而另一個線程經常會調用foo1::get_result()。爲了優化,我認爲它可以使用原子來應用雙重檢查鎖定模式(DCLP),如版本2所示。對於這種情況,還有其他更好的設計嗎?或者可以改進它,例如使用std::memory_order訪問原子?在這種情況下,'雙重檢查鎖定模式'是否適合std :: mutex?

VERSION1

class data {}; 
class some_data {}; 
class some_result {}; 

class foo1 
{ 
public: 
    foo1() : m_bNeedUpdate(false) {} 

    void add_data(data n) 
    { 
     std::lock_guard<std::mutex> lock(m_mut); 

     // ... restore new data to m_SomeData 

     m_bNeedUpdate = true; 
    } 

    some_result get_result() const 
    { 
     { 
      std::lock_guard<std::mutex> lock(m_mut); 
      if (m_bNeedUpdate) 
      { 
       // ... process mSomeData and update m_SomeResult 

       m_bNeedUpdate = false; 
      } 
     } 
     return m_SomeResult; 
    } 

private: 
    mutable std::mutex m_mut; 
    mutable bool  m_bNeedUpdate; 
    some_data   m_SomeData; 

    mutable some_result m_SomeResult; 
}; 

版本2

class foo2 
{ 
public: 
    foo2() : m_bNeedUpdate(false) {} 

    void add_data(data n) 
    { 
     std::lock_guard<std::mutex> lock(m_mut); 

     // ... restore new data to m_SomeData 

     m_bNeedUpdate.store(true); 
    } 

    some_result get_result() const 
    { 
     if (m_bNeedUpdate.load()) 
     { 
      std::lock_guard<std::mutex> lock(m_mut); 
      if (m_bNeedUpdate.load()) 
      { 
       // ... process mSomeData and update m_SomeResult 

       m_bNeedUpdate.store(false); 
      } 
     } 
     return m_SomeResult; 
    } 

private: 
    mutable std::mutex   m_mut; 
    mutable std::atomic<bool> m_bNeedUpdate; 
    some_data     m_SomeData; 

    mutable some_result   m_SomeResult; 
}; 
+0

除了'm_bNeedUpdate',您還需要對'm_SomeResult'進行原子訪問,否則就會出現競爭(線程1開始複製'm_SomeResult',線程2中斷它並開始更新)。假設'm_SomeResult'是不可或缺的,只需使它成爲原子;否則,你是恕我直言更好的標準讀寫鎖(我認爲它可以用雙重檢查鎖,但它很複雜,不值得)。 –

+0

如果只有一個線程調用'foo2 :: get_result()',它仍然是競爭條件嗎? – cbel

+0

可能不是這種情況。 –

回答

2

問題是根據C++ 11(和Posix,較早),版本2不是線程安全的,至少是 ;您正在訪問 變量,該變量可能會被修改,而訪問不受 保護。 (雙重檢查鎖定模式被稱爲是 壞了,看到 http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf) 它可以進行工作在C++ 11 (或更早非便攜)使用原子變量,但你什麼書面結果 未定義的行爲。

+0

通過使用原子變量是否工作(不會中斷)?對不起,我不明白。 – cbel

+0

可以使用原子變量進行工作。 –

+0

你的意思是讓'm_SomeResult'成爲原子嗎?另一個問題,如果只有一個調用'foo2 :: get_result()'的線程,'m_SomeResult'需要被原子化? – cbel

1

我覺得顯著改善(代碼大小方面以及在簡單性和性能方面)可以實現通過使用允許許多線程並行讀取的'讀寫鎖'。 Boost爲此提供了shared_mutex,但從快速瀏覽看來,this blog article似乎以不需要Boost的便攜方式實現了相同類型的鎖定。

+0

我對你引用的文章中的實現有懷疑,你真的需要一個條件變量來避免競爭條件。 –

+0

@JamesKanze很可能這篇文章中的代碼是有缺陷的,我沒有花時間仔細檢查它,因爲它對於多線程代碼來說是非常必要的。如果你認爲這篇文章給出了不好的建議,隨時建議替代方案。我只是抓住了第一次谷歌命中無助的實現,看起來並不完全瘋狂。 –

+0

我還沒有詳細分析它,但一般來說,當你需要控制對多個變量的訪問時,你需要一個條件變量。而且或多或少的規範實現使用條件變量。 (爲什麼文章沒有使用規範的實現,我不知道。) –

0

你說你經常打電話給get_average,你有沒有考慮只根據你沒有看過的數字計算平均值?它將是O(n)而不是O(n^2)。

它會像

average = (last_average * last_size + static_cast<double>(
      std::accumulate(m_vecData.begin() + last_size, m_vecData.end(), 0)))/
      m_vecData.size(); 

它應該給你滿意的結果,這取決於你的載體有多大。

+0

對不起,我沒有說乾淨,平均的計算並不重要。我應該糾正這是一些任務,但不是平均水平。我將編輯。 – cbel

+0

啊,對不起。 –

相關問題