簡體   English   中英

如何編寫安全的原子對象包裝器?

[英]How is it possible to write a safe atomic object wrapper?

我一直在嘗試編寫包裝器類來包裝Win32內在函數,例如InterlockedIncrementInterlockedExchange 盡管我的問題可能與其他支持相似內在函數的平台相似。

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

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.

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