2013-05-15 25 views
1

我一直在試圖編寫一個包裝類來包裝Win32內在函數,如InterlockedIncrement,InterlockedExchange。雖然我的問題可能與支持類似內部函數的其他平臺類似。如何編寫安全的原子對象包裝?

我有一個基本的模板類型:

template <typename T, size_t W = sizeof(T)> 
class Interlocked {}; 

這部分是專門爲不同大小的數據類型。例如,這裏的32位之一:

// 
// Partial specialization for 32 bit types 
// 
template<typename T> 
class Interlocked <T, sizeof(__int32)> 
{ 
public: 

    Interlocked<T, sizeof(__int32)>() {}; 

    Interlocked<T, sizeof(__int32)>(T val) : m_val(val) {} 

    Interlocked<T, sizeof(__int32)>& Interlocked<T, sizeof(__int32)>::operator= (T val) 
    { 
     InterlockedExchange((LONG volatile *)&m_val, (LONG)val); 
     return *this; 
    } 

    Interlocked<T, sizeof(__int32)> Interlocked<T, sizeof(__int32)>::operator++() 
    { 
     return static_cast<T>(InterlockedIncrement((LONG volatile *)&m_val)); 
    } 

    Interlocked<T, sizeof(__int32)> Interlocked<T, sizeof(__int32)>::operator--() 
    { 
     return static_cast<T>(InterlockedDecrement((LONG volatile *)&m_val)); 
    } 

    Interlocked<T, sizeof(__int32)>& Interlocked<T, sizeof(__int32)>::operator+(T val) 
    { 
     InterlockedExchangeAdd((LONG volatile *)&m_val, (LONG) val); 
     return *this; 
    } 

    Interlocked<T, sizeof(__int32)>& Interlocked<T, sizeof(__int32)>::operator-(T val) 
    { 
     InterlockedExchangeSubtract((LONG volatile *)&m_val, (LONG) val); 
     return *this; 
    } 

    operator T() 
    { 
     return m_val; 
    } 

private: 

    T m_val; 
}; 

不過,我來,我不知道如何安全地寫出這樣的對象的結論。具體來說,我已經意識到,在執行互鎖操作後返回*this允許另一個線程在返回之前更改該變量。這將使類型的點無效。可以寫這樣的東西嗎?大概的std ::原子解決了這個問題,但我沒有訪問,在我的編譯器...

回答

7

如果沒有std::atomic,則可以使用boost::atomic(出現在最新Boost 1.53),這是很好經過測試的跨平臺實施。

+0

謝謝我沒有意識到'boost :: atomic'。儘管我仍然想知道理論上如何做到這一點。 – Benj

+0

@Benj:好的,我明白了。你可以看看它是如何在Boost中實現的 – nogard

2

運營商+-是沒有意義的。實際上實現的內容更像複合賦值(+=,-=),但您需要返回類型爲T的值,而不是對(*this)的引用。當然,這並不遵循約定賦值運算符... std::atomic選擇使用命名函數而不是運算符重載除了++--,可能是出於這個原因。

0

除了來自nogard的「使用其他人的已經測試和工作的實現」的非常好的建議,我建議您不要返回*this,但操作的結果 - 這是現有的互鎖方式操作符工作(以及std :: atomic如何工作)。

因此,換句話說,你的操作員代碼應該是這樣的:

T Interlocked<T, sizeof(__int32)>::operator+(T val) 
{ 
    return InterlockedExchangeAdd((LONG volatile *)&m_val, (LONG) val); 
} 

這裏有一個問題,因爲本·福格特poinst的是,這個函數修改輸入值,這意味着:

a = b + c; 

居然會做:

b += c; 
a = b; 
+0

我曾嘗試過,但是當然如果你沒有從賦值(例如)返回this,那麼你的對象不會像普通類型那樣工作。 – Benj

+0

我懷疑是否有合理的方法可以使用'Interlocked a,b,c; ... a = b + c;'並以任何有意義的方式依靠結果。 –

+0

@Benj:一個原子類型*不是一個正常類型,那爲什麼它應該像一個一樣?包裝的目標應該是使其更易於使用,而不是將其原子性抽象出來。 –

0

考慮兩個線程執行濃在你的原子序數類中添加urrent,其中Thread#n將t_n添加到您的編號x中。

您擔心在一個線程中執行加法和返回結果之間,第二個線程可能執行加法,從而搞亂了第一個線程的返回值。

該類的用戶觀察到的行爲是返回值是(x + t_1 + t_2)而不是預期的(x + t_1)

現在讓我們假設你有一個不允許這種行爲的實現,即結果保證爲(x_1 + t_1),其中x_1是緊接線程#1執行其添加之前的數值。

如果線程#2之前線程#1立即執行的同時加入你得到的價值是:

(x_1 + t_1) = ((x + t_2) + t_1) 

哪個是完全相同的比賽。除非您在應用添加之前引入一些額外的同步或檢查數字的預期值,否則您將始終得到此比賽。

1

你有一個數據的比賽在你的代碼

可以同時寫一個變量(使用InterlockedBlah(...)),並使用運營商T.

的內存模型C +從中讀+11表示這是不允許的。你可能會依賴於你的平臺的硬件規範,它可能會聲明4字節(對齊!)的內容不會被撕掉,但這最多隻會是脆弱的。未定義的行爲是不確定的。

此外,讀取沒有任何內存障礙[它告訴編譯器和硬件]不要重新排序指令。

使讀取返回InterlockedAdd(& val,0)操作可能會解決所有這些問題,因爲Windows上的互鎖API會保證添加正確的內存屏障。但是,請注意其他MS平臺上沒有此保證的Interlocked * API。

基本上你想要做的事情可能是可能的,但真的很難,並且肯定依賴於軟件和硬件在每個平臺上的保證 - 這是不可能以便攜的方式寫的。

使用std :: atomic,使用boost :: atomic