![](/img/trans.png)
[英]Is it possible to make atomic operations on singleton object thread safe automatically
[英]How is it possible to write a safe atomic object wrapper?
我一直在嘗試編寫包裝器類來包裝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 :: atomic解決了這個問題,但我無法在編譯器中訪問它...
如果您沒有std::atomic
,則可以使用boost::atomic
(出現在最新的Boost 1.53中 ),它已經過跨平台實現的良好測試。
運算符+
和-
沒有意義。 您實際實現的內容更像是復合賦值( +=
, -=
),但是您需要返回類型T
的值,而不是對(*this)
的引用。 當然,這並不遵循賦值運算符的約定... std::atomic
選擇使用命名函數,而不是除++
和--
以外的所有內容都使用運算符重載,可能是出於這個原因。
您的代碼中存在數據爭用
您可以同時寫入變量(使用InterlockedBlah(...))並使用運算符T從中讀取。
C ++ 11的內存模型指出這是不允許的。 您可能會依賴於平台的硬件規格,該規格可能會指出4字節(對齊!)的讀取不會撕裂,但這充其量是脆弱的。 未定義的行為是未定義的。
而且,讀取沒有任何內存障礙[告訴編譯器和硬件]不要重新排序指令。
使讀取返回InterlockedAdd(&val,0)操作可能會解決所有這些問題,因為Windows上的Interlocked API可以確保添加正確的內存屏障。 但是,請注意沒有此保證的其他MS平台上的Interlocked * API。
基本上,您要嘗試執行的操作可能是可行的,但確實很難,並且絕對依賴於每個平台上的軟件和硬件保證-不可能以可移植的方式編寫此代碼。
使用std :: atomic,使用boost :: atomic
除了nogard的“使用別人已經過測試並正在執行的實現”這非常好的建議之外,我建議您不要返回*this
,而是操作的結果-這是現有的互鎖操作符的工作方式(以及std :: atomic的工作原理)。
因此,換句話說,您的操作員代碼應如下所示:
T Interlocked<T, sizeof(__int32)>::operator+(T val)
{
return InterlockedExchangeAdd((LONG volatile *)&m_val, (LONG) val);
}
有一個問題,因為Ben Voigt指出該函數會修改輸入值,這意味着:
a = b + c;
實際上會做:
b += c;
a = b;
考慮兩個線程在您的原子數類上執行並發加法,其中線程#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)
這是完全相同的種族 。 除非您進行一些其他同步或在應用加法之前檢查數字的期望值,否則您將始終處於競爭狀態。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.