5

汎用フィールドとそれをカプセル化するプロパティがあります。

T item;

public T Item
{
    get { return item; }
    set { item = value; }
}

問題は、このプロパティが1つのスレッドから書き込まれ、同時に複数のスレッドから読み取られる可能性があることです。また、Tstruct、またはの場合long、読者は一部が古い値で一部が新しい値の結果を取得する可能性があります。どうすればそれを防ぐことができますか?

を使用してみvolatileましたが、それは不可能です。

揮発性フィールドのタイプを「T」にすることはできません。

これは私がすでに書いたコードのより単純なケースであり、を使用ConcurrentQueue<T>しているので、ここでも使用することを考えました。

ConcurrentQueue<T> item;

public T Item
{
    get
    {
        T result;
        item.TryPeek(out result);
        return item;
    }

    set
    {
        item.TryEnqueue(value);
        T ignored;
        item.TryDequeue(out ignored);
    }
}

これはうまくいくでしょうが、それは単純なはずの何かに対する過度に複雑な解決策であるように私には思えます。

パフォーマンスは重要であるため、可能であれば、ロックは回避する必要があります。

setと同時に発生した場合、古い値を返すか新しい値を返すgetかは関係ありません。get

4

3 に答える 3

3

それは完全にタイプに依存しTます.

class制約を設定できる場合、この特定のケースで は何もTする必要はありません。参照代入はアトミックです。これは、基になる変数への部分的または破損した書き込みを行うことができないことを意味します。

同じことが読み取りにも当てはまります。部分的に書かれたリファレンスを読むことはできません。

が構造体の場合T、次の構造体のみをアトミックに読み取り/代入できます (C# 仕様のセクション 12.5 によると、強調鉱山も上記のステートメントを正当化します)。

bool、char、byte、sbyte、short、ushort、uint、int、float、および参照型のデータ型の読み取りと書き込みはアトミックである必要があります。さらに、前のリストの基になる型を持つ列挙型の読み取りと書き込みもアトミックである必要があります。long、ulong、double、decimal などの他の型、およびユーザー定義型の読み取りと書き込みは、アトミックである必要はありません。その目的のために設計されたライブラリ関数は別として、インクリメントまたはデクリメントの場合のように、アトミックな読み取り-変更-書き込みの保証はありません。

したがって、読み取り/書き込みを行おうとしているだけで、上記の条件のいずれかを満たしている場合は、何もする必要はありません (ただし、 type にも制約を設定する必要があることを意味しますT)。

の制約を保証できない場合は、アクセスを同期するステートメントTのようなものに頼る必要があります (前述の読み取りと書き込みの場合)。lock

lockステートメント (実際にはMonitorclass ) を使用するとパフォーマンスが低下することがわかった場合は、が重すぎる場所で役立つことを意図しているため、structureを使用SpinLockできます。Monitor

T item;

SpinLock sl = new SpinLock();

public T Item
{
    get 
    { 
        bool lockTaken = false;

        try
        {
            sl.Enter(ref lockTaken);
            return item; 
        }
        finally
        {
            if (lockTaken) sl.Exit();
        }
    }
    set 
    {
        bool lockTaken = false;

        try
        {
            sl.Enter(ref lockTaken);
            item = value;
        }
        finally
        {
            if (lockTaken) sl.Exit();
        }
    }
}

ただし、待機時間が長すぎるとのパフォーマンスSpinLockが低下し、クラスと同じになる可能性がMonitorあるため、注意してください。もちろん、単純な割り当て/読み取りを使用していることを考えると、それほど長くはかからないはずです(コピーのセマンティクスのためにサイズが非常に大きい構造を使用している場合を除きます)。

もちろん、このクラスが使用されると予測される状況でこれを自分でテストし、どのアプローチが自分 (lockまたはSpinLock構造) に最適かを確認する必要があります。

于 2012-07-18T15:20:04.410 に答える
3

当初は を検討していましたが、参照型に制限されていないためInterlocked、ここでは実際には役に立たないと思います。T(もしそうなら、アトミック性はすでに問題ないでしょう。)

正直なところ、ロックから始めて、パフォーマンスを測定します。ロックが競合していない場合、それは非常に安価なはずです。最も単純なソリューションが遅すぎることが証明された場合にのみ、より難解になることを検討してください。

基本的に、これが単純であるというあなたの期待は、ここでの制約のない一般性のために失敗します.最も効率的な実装は型によって異なります.

于 2012-07-18T15:13:02.340 に答える
-2

なぜこれをまったく保護する必要があるのですか?

変数の参照インスタンスの変更は、アトミック操作です。したがって、何を読んでgetも無効にはなりません。setが同時に実行されている場合、古いインスタンスか新しいインスタンスかはわかりません。しかし、それ以外は大丈夫です。

パーティション I、CLI 仕様のセクション 12.6.6 は次のように述べています。 ."

また、変数は参照型であるため、常にネイティブ ワードのサイズになります。したがって、次のようなことをしても、結果が無効になることはありません。

Private T _item;
public T Item
{
    get
    {
        return _item;
    }

    set
    {
        _item = value
    }
}

一般的なものに固執し、それをすべてに使用したい場合の例。このアプローチは、キャ​​リア ヘルパー クラスを使用することです。パフォーマンスは大幅に低下しますが、ロックフリーになります。

Public Foo
{
    Private Carrier<T> 
    {
        T _item
    }

    Private Carrier<T> _item;
    public T Item
    {
        get
        {
            Dim Carrier<T> carrier = _item;
            return carrier.item;
        }



set
    {
        Dim Carrier<T> carrier = new Carrier<T>();
        carrier.item = value;
        _item = carrier;
    }
}

}

このようにして、参照される型を常に使用し、アクセスがロックされないようにすることができます。欠点は、すべての集合操作がガベージを作成することです。

于 2012-07-18T15:13:18.923 に答える