2010-07-29 99 views
2

我有一些數據是由多個線程讀取和更新。讀取和寫入都必須是原子的。我想這樣做是這樣的:無鎖讀寫器

// Values must be read and updated atomically 
struct SValues 
{ 
    double a; 
    double b; 
    double c; 
    double d; 
}; 

class Test 
{ 
public: 
    Test() 
    { 
     m_pValues = &m_values; 
    } 

    SValues* LockAndGet() 
    { 
     // Spin forver until we got ownership of the pointer 
     while (true) 
     { 
      SValues* pValues = (SValues*)::InterlockedExchange((long*)m_pValues, 0xffffffff); 
      if (pValues != (SValues*)0xffffffff) 
      { 
       return pValues; 
      } 
     } 
    } 

    void Unlock(SValues* pValues) 
    { 
     // Return the pointer so other threads can lock it 
     ::InterlockedExchange((long*)m_pValues, (long)pValues); 
    } 

private: 
    SValues* m_pValues; 
    SValues m_values; 
}; 

void TestFunc() 
{ 
    Test test; 

    SValues* pValues = test.LockAndGet(); 

    // Update or read values 

    test.Unlock(pValues); 
} 

的數據被竊取的指針,它保護每一個讀取和寫入,這應該使線程安全的,但它需要每一個訪問提供了兩個互鎖的指令。將有大量的讀取和寫入,並且我不能預先告知是否會有更多的讀取或更多的寫入。

它可以做得比這更有效嗎?這在閱讀時也會鎖定,但由於可能有更多的寫入,所以閱讀沒有任何優化閱讀的意義,除非它不會對寫作造成損失。

我正在考慮在沒有互鎖指令(連同序列號)的情況下獲取指針的讀取操作,複製數據,然後有一種方法告訴序列號是否已更改,在這種情況下應該重試。但是,這需要一些記憶障礙,我不知道它是否能夠提高速度。

-----編輯-----

感謝所有的好評!我實際上沒有運行這個代碼,但我會嘗試今天晚些時候將當前方法與關鍵部分進行比較(如果我獲得時間)。我仍然在尋找最佳的解決方案,所以稍後我會回到更高級的評論。再次感謝!

+0

什麼是使用默認的線程同步原語的問題? – naivnomore 2010-07-29 06:29:07

+0

我必須承認,我只是假設我能做得更快。 1)我只在這裏顯示一個實例,但實際上我可能會有這些受保護的數據記錄的10000個實例,這將意味着10000個關鍵部分。但也許這不是問題,我不知道,我從來沒有嘗試過這樣的事情。 2)我希望我能拿出比關鍵部分更快的東西。每秒可以輕鬆實現數百萬次讀/寫。而在個人層面上,我認爲讓它像人性化(機器化)一樣快是有趣的。 – Rabbit 2010-07-29 06:49:06

+0

Windows CRITICAL_SECTION非常輕便,除非實際上必須阻止。我不認爲像這樣忙着等待用戶線程是一個非常好的主意 - 你隱式地告訴調度程序,你有很多事情要做,而實際上情況正好相反。 – 2010-07-29 07:46:54

回答

3

你寫的東西基本上是螺旋鎖。如果你打算這麼做,那麼你可能只需要使用一個互斥體,比如boost::mutex。如果你真的想要一個自旋鎖,使用一個系統提供的,或者一個庫,而不是自己寫。

其他可能性包括做某種形式的寫入時複製。通過指針存儲數據結構,並在讀取側讀取指針(原子)。在寫入端創建一個新實例(根據需要複製舊數據)並原子交換指針。如果寫入確實需要舊值,並且有多個寫入者,那麼您需要執行比較交換循環以確保自讀取該值以來該值沒有發生變化(小心ABA問題),或者爲作家。如果你這樣做,那麼你需要小心你如何管理內存 - 當沒有線程引用它時(但之前沒有),你需要一些方法來回收數據的實例。

+0

沒錯,它是一個螺旋鎖。經過一番研究後,似乎可以在退出時不需要鎖定指令就可以實現自旋鎖,並且在旋轉時不需要寫任何東西。這可能是我正在尋找的解決方案。我現在很忙,但稍後我會回頭看看。 – Rabbit 2010-07-29 08:57:51

+0

到目前爲止,我的最佳解決方案是使用我需要保護的每個指針的自旋鎖。它們每個只使用4個字節。它與本問題中的示例代碼基本相同,區別在於自旋鎖在旋轉時不會使用互鎖指令,並且它使用asm暫停指令。 – Rabbit 2010-07-29 19:21:53

2

有幾種方法可以解決這個問題,特別是沒有互斥或鎖定機制。問題是我不確定你的系統上的約束是什麼。

請記住,原子操作是經常被C++編譯器移動的東西。通過在每個寫線程1單生產者單消費者

多生產單消費:

一般來說,我會解決這樣的問題。每個線程寫入他們自己的隊列中。單個消費者線程收集生成的數據並將其存儲在單個消費者多讀取器數據存儲中。對此的實現是很多工作,只有在您正在做一個時間關鍵型應用程序並且您有時間投入此解決方案時才推薦。

還有更多的事情來讀了這一點,因爲執行是特定於平臺:

原子等在Windows/XBOX360操作: http://msdn.microsoft.com/en-us/library/ee418650(VS.85).aspx

多線程的單生產者單消費者無鎖:
http://www.codeproject.com/KB/threads/LockFree.aspx#heading0005

什麼 「揮發性」 還真是,可以用於:
http://www.drdobbs.com/cpp/212701484

香草薩特寫了,提醒你寫這種代碼的危險的好文章: http://www.drdobbs.com/cpp/210600279;jsessionid=ZSUN3G3VXJM0BQE1GHRSKHWATMY32JVN?pgno=2

+0

另一個非鎖定隊列的優秀解決方案: http://www.drdobbs.com/architecture-and-design/210604448 – Simon 2010-07-29 07:54:52

+0

請注意比賽條件。 – Simon 2010-07-29 07:56:56