1

InterlockedIncrement、などのWin32組み込み関数をラップするラッパークラスを作成しようとしています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 はこの問題を解決しますが、コンパイラではアクセスできません...

4

5 に答える 5

2

演算子+-は無意味です。+=実際に実装したものは、複合代入 ( , )のように見えますが、 への参照ではなく-=型の値を返す必要があります。もちろん、これは代入演算子の規則に従っていません...おそらくその理由で、andを除くすべての演算子のオーバーロードではなく、名前付き関数を使用することを選択します。T(*this)std::atomic++--

于 2013-05-15T13:14:10.233 に答える
1

コードにデータ競合があります

変数への書き込み (InterlockedBlah(...) を使用) と、演算子 T を使用した変数からの読み取りを同時に行うことができます。

C++11 のメモリ モデルでは、これは許可されていないと記載されています。プラットフォームのハードウェア仕様に依存する可能性があります。これは、4 バイトの (整列された!) 読み取りは引き裂かないと述べている可能性がありますが、これはせいぜい脆弱です。そして、未定義の動作は未定義です。

また、読み取りには、[コンパイラとハードウェアの両方に命令を並べ替えないように指示する] メモリ バリアがありません。

ウィンドウのインターロック API は適切なメモリ バリアを追加することが保証されているため、読み取りを return InterlockedAdd(&val, 0) 操作にすることで、これらすべてが解決される可能性があります。ただし、この保証がない他の MS プラットフォームの Interlocked* API には注意してください。

基本的に、あなたがやろうとしていることはおそらく可能ですが、本当に難しく、各プラットフォームでソフトウェアとハ​​ードウェアが保証するものに依存しています.これを移植可能な方法で書くことは不可能です.

std::atomic を使用、boost::atomic を使用

于 2013-05-15T13:39:57.293 に答える
0

原子番号クラスで同時に加算を実行する 2 つのスレッドを考えてみましょう。スレッド #n はt_nnumber に amount を加算しますx

1 つのスレッドで加算を実行して結果を返す間に、2 番目のスレッドが加算を実行し、最初のスレッドの戻り値が台無しになるのではないかと心配しています。

このクラスのユーザーの観察された動作は、戻り値が(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)

まったく同じ人種です。加算を適用する前に、追加の同期または数値の期待値のチェックを導入しない限り、常にこの競合が発生します。

于 2013-05-15T13:31:31.010 に答える
0

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;
于 2013-05-15T13:14:59.417 に答える