5

最近、C#コードの一部をリファクタリングしていて、いくつかのダブルチェックされたロック方法が実行されていることに気付きました。当時はそれが悪い習慣だとは知らなかったので、本当にそれを取り除きたいと思っています。

問題は、怠惰に初期化され、多くのスレッドによって頻繁にアクセスされる必要があるクラスがあることです。また、初期化されたオブジェクトがメモリ内に長く留まらないようにするために弱参照を使用することを計画しているため、初期化を静的初期化子に移動したくありません。ただし、必要に応じて、オブジェクトを「復活」させて、これがスレッドセーフな方法で行われるようにします。

C#でReaderWriterLockSlimを使用し、最初のチェックの前にUpgradeableReadLockを入力してから、必要に応じて初期化用の書き込みロックを入力することが許容できる解決策になるかどうか疑問に思いました。これが私が念頭に置いていることです:

public class LazyInitialized
{
    private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

    private volatile WeakReference _valueReference = new WeakReference(null);
    public MyType Value
    {
        get
        {
            MyType value = _valueReference.Target as MyType;
            _lock.EnterUpgradeableReadLock();
            try
            {
                if (!_valueReference.IsAlive) // needs initializing
                {
                    _lock.EnterWriteLock();
                    try
                    {
                        if (!_valueReference.IsAlive) // check again
                        {
                            // prevent reading the old weak reference
                            Thread.MemoryBarrier(); 
                            _valueReference = new WeakReference(value = InitializeMyType());
                        }
                    }
                    finally
                    {
                        _lock.ExitWriteLock();
                    }
                }
            }
            finally
            {
                _lock.ExitUpgradeableReadLock();
            }
            return value;
        }       
    }

    private MyType InitializeMyType()
    {
        // code not shown    
    }
}

私のポイントは、他のスレッドがアイテムをもう一度初期化しようとするべきではないということですが、値が初期化されると、多くのスレッドが同時に読み取る必要があります。アップグレード可能な読み取りロックは、書き込みロックが取得された場合にすべてのリーダーをブロックする必要があるため、オブジェクトの初期化中の動作は、アップグレード可能な読み取りロックが開始されるロックステートメントの場合と同様になります。初期化後、アップグレード可能な読み取りロックは複数のスレッドを許可するため、各スレッドを待機することによるパフォーマンスへの影響はありません。

また、ここでvolatileを使用すると、読み取り前と書き込み後にメモリバリアが自動的に挿入されるという記事を読みました。したがって、_valueReferenceオブジェクトが正しく読み取られるようにするには、読み取りと書き込みの間に手動で定義されたバリアが1つだけあれば十分だと思います。このアプローチを使用することについてのあなたのアドバイスと批判に喜んで感謝します。

4

2 に答える 2

2

警告:一度に1つのスレッドのみがUpgradeableReadLockモードに入ることができます。ReaderWriterLockSlimをチェックしてください。したがって、最初のスレッドが書き込みモードに入ってオブジェクトを作成しているときにスレッドが積み重なると、バックアップが(うまくいけば)解決されるまでボトルネックが発生します。静的初期化子を使用することを真剣に提案します。これにより、作業が楽になります。

編集:オブジェクトを再作成する必要がある頻度に応じて、実際にはMonitorクラスとそのWaitメソッドおよびPulseメソッドを使用することをお勧めします。値を再作成する必要がある場合は、スレッドにオブジェクトを待機させ、別のオブジェクトをパルスして、ワーカースレッドにウェイクアップして新しいオブジェクトを作成する必要があることを通知します。オブジェクトが作成されると、PulseAllは、すべてのリーダースレッドがウェイクアップして新しい値を取得できるようにします。(理論的には)

于 2011-06-13T18:54:44.853 に答える
2

@Mannimarcoのポイントを強調すると、これがValueへの唯一のアクセスポイントであり、そのように見える場合、ReaderWriterLockSlimのセットアップ全体は、単純なMonitor.Enter/Monitor.Leaveアプローチに勝るものはありません。しかし、それははるかに複雑です。

したがって、次のコードは機能と効率が同等であると思います。

private WeakReference _valueReference = new WeakReference(null);
private object _locker = new object();

public MyType Value
{    
  get
  {    
    lock(_locker)  // also provides the barriers
    {
        value = _valueReference.Target;

        if (!_valueReference.IsAlive)
        {
            _valueReference = new WeakReference(value = InitializeMyType());
        }
        return value; 
    }
  }    
}
于 2011-06-13T19:02:15.077 に答える