6

2 つのメソッドを持つクラスを作成したいと思います。

  • void SetValue(T value)値を保存しますが、単一の値のみを保存できます (それ以外の場合は例外がスローされます)。
  • T GetValue()値を取得します (まだ値がない場合は例外をスローします)。

私には次の欲求/制約があります。

  • 値の読み取りは安価なはずです。
  • 値の書き込みは (適度に) コストがかかる場合があります。
  • GetValue()最新の値が存在しない場合にのみ例外をスローする必要があります ( ):別のスレッドでを呼び出した後、null古い値に基づいて例外をスローするべきではありません。nullSetValue()
  • 値は一度だけ書き込まれます。これはGetValue()、値が null でない場合、値を更新する必要がないことを意味します。
  • 完全なメモリ バリアを回避できる場合は、(はるかに) 優れています。
  • ロックフリーの並行性の方が優れていることはわかりますが、ここでそうであるかどうかはわかりません。

私はそれを達成するためのいくつかの方法を思いつきましたが、どれが正しいのか、どれが効率的なのか、なぜそれらが (不) 正しくて (非) 効率的なのか、そして私が望むものを達成するためのより良い方法があるかどうかはわかりません.

方法 1

  • 不揮発性フィールドの使用
  • Interlocked.CompareExchangeフィールドへの書き込みに使用する
  • Interlocked.CompareExchangeフィールドから読み取るために使用する
  • Interlocked.CompareExchange(ref v, null, null)これは、フィールドに対して を実行した後、次のアクセスで、少なくとも見た値と同じくらい新しい値が取得されるという (おそらく間違った) 仮定に依存していInterlocked.CompareExchangeます。

コード:

public class SetOnce1<T> where T : class
{
    private T _value = null;

    public T GetValue() {
        if (_value == null) {
            // Maybe we got a stale value (from the cache or compiler optimization).
            // Read an up-to-date value of that variable
            Interlocked.CompareExchange<T>(ref _value, null, null);
            // _value contains up-to-date data, because of the Interlocked.CompareExchange call above.
            if (_value == null) {
                throw new System.Exception("Value not yet present.");
            }
        }

        // _value contains up-to-date data here too.
        return _value;
    }

    public T SetValue(T newValue) {
        if (newValue == null) {
            throw new System.ArgumentNullException();
        }

        if (Interlocked.CompareExchange<T>(ref _value, newValue, null) != null) {
            throw new System.Exception("Value already present.");
        }

        return newValue;
    }
}

方法 2

  • volatileフィールドの使用
  • Ìnterlocked.CompareExchange to write the value (with [Joe Duffy](http://www.bluebytesoftware.com/blog/PermaLink,guid,c36d1633-50ab-4462-993e-f1902f8938cc.aspx)'s#pragma to avoid the compiler warning on passing a volatile value byref`) を使用します。
  • フィールドがvolatile

コード:

public class SetOnce2<T> where T : class
{
    private volatile T _value = null;

    public T GetValue() {
        if (_value == null) {
            throw new System.Exception("Value not yet present.");
        }
        return _value;
    }

    public T SetValue(T newValue) {
        if (newValue == null) {
            throw new System.ArgumentNullException();
        }

        #pragma warning disable 0420
        T oldValue = Interlocked.CompareExchange<T>(ref _value, newValue, null);
        #pragma warning restore 0420

        if (oldValue != null) {
            throw new System.Exception("Value already present.");
        }
        return newValue;
    }
}

方法 3

  • 不揮発性フィールドの使用。
  • 書き込み時にロックを使用します。
  • null を読み取る場合は、読み取り時にロックを使用します (参照はアトミックに読み取られるため、ロックの外で一貫した値を取得します)。

コード:

public class SetOnce3<T> where T : class
{
    private T _value = null;

    public T GetValue() {
        if (_value == null) {
            // Maybe we got a stale value (from the cache or compiler optimization).
            lock (this) {
                // Read an up-to-date value of that variable
                if (_value == null) {
                    throw new System.Exception("Value not yet present.");
                }
                return _value;
            }
        }
        return _value;
    }

    public T SetValue(T newValue) {
        lock (this) {
            if (newValue == null) {
                throw new System.ArgumentNullException();
            }

            if (_value != null) {
                throw new System.Exception("Value already present.");
            }

            _value = newValue;

            return newValue;
        }
    }
}

方法 4

  • volatile フィールドの使用
  • ロックを使用して値を書き込みます。
  • フィールドは揮発性であるため、値を直接読み取ります (参照はアトミックに読み取られるため、ロックを使用しなくても一貫した値を取得できます)。

コード:

public class SetOnce4<T> where T : class
{
    private volatile T _value = null;

    public T GetValue() {
        if (_value == null) {
            throw new System.Exception("Value not yet present.");
        }
        return _value;
    }

    public T SetValue(T newValue) {
        lock (this) {
            if (newValue == null) {
                throw new System.ArgumentNullException();
            }

            if (_value != null) {
                throw new System.Exception("Value already present.");
            }

            _value = newValue;

            return newValue;
        }
    }
}

その他の方法

Thread.VolatileRead()また、任意の書き込み手法と組み合わせて、値を読み取るために使用することもできます。

4

2 に答える 2

1

まあ、揮発性についてはわかりませんが、少し乱用して2番目のメソッドを呼び出すことを気にしないのであれば...(また、null可能性に依存していません。値の型に自由に使用できます)ゲッターでもnullチェックを回避します。書き込み時にロックのみが行われるため、私の知る限り、唯一のマイナスの影響は、値を取得するときにデリゲートを呼び出すことです。

public class SetOnce<T>
{
    private static readonly Func<T> NoValueSetError = () => { throw new Exception("Value not yet present.");};

    private Func<T> ValueGetter = NoValueSetError;
    private readonly object SetterLock = new object();

    public T SetValue(T newValue)
    {
        lock (SetterLock)
        {
            if (ValueGetter != NoValueSetError)
                throw new Exception("Value already present.");
            else
                ValueGetter = () => newValue;
        }

        return newValue;
    }

    public T GetValue()
    {
        return ValueGetter();
    }
}

実際、私はこれについて本当にばかげていると感じ、少し虐待を感じています. これを行う際の潜在的な問題についてのコメントを知りたいです。:)

SetValue(null)編集:これは、への最初の呼び出しが「null」が有効な値と見なされ、例外なく null を返すことを意味することに気付きました。これがあなたが望むものかどうかはわかりません(なぜnull有効な値にならないのかわかりませんが、それを避けたい場合は、セッターでチェックを行うだけです。ゲッターは必要ありません)

EDITx2: それでも値を制限してclass回避したい場合は、次nullのように簡単に変更できます。

public class SetOnce<T> where T : class
{
    private static readonly Func<T> NoValueSetError = () => { throw new Exception("Value not yet present.");};

    private Func<T> ValueGetter = NoValueSetError;
    private readonly object SetterLock = new object();

    public T SetValue(T newValue)
    {
        if (newValue == null)
            throw new ArgumentNullException("newValue");

        lock (SetterLock)
        {
            if (ValueGetter != NoValueSetError)
                throw new Exception("Value already present.");
            else
                ValueGetter = () => newValue;
        }

        return newValue;
    }

    public T GetValue()
    {
        return ValueGetter();
    }
}
于 2013-02-21T15:13:02.400 に答える
0

(#3)以外の方法はどれもlock正しく機能しません。

見て:

    if (_value == null) {
        throw new System.Exception("Value not yet present.");
    }
    return _value;

このコードは、 内にない場合、アトミックではなく、スレッドセーフではありませんlock。他のスレッドがとの間に設定_valueされる可能性はまだあります。あなたができることは、ローカル変数に設定することです:nullifreturn

    var localValue = _value;
    if (localValue == null) {
        throw new System.Exception("Value not yet present.");
    }
    return localValue;

それでもストール値を返す可能性があります。lock明確で、簡単で、高速です。

編集:は外部に表示され、サードパーティのコードがオブジェクトを決定する可能性があるlock(this)ため、使用は避けてください。thislock

編集 2: null 値を設定できない場合は、次のようにします。

public T GetValue() {
    if (_value == null) {
        throw new System.Exception("Value not yet present.");
    }
    return _value;
}

public T SetValue(T newValue) {
    lock (writeLock)
    {        
        if (newValue == null) {
            throw new System.ArgumentNullException();
        }
        _value = newValue;
        return newValue;
    }
}
于 2013-02-21T14:49:46.373 に答える