再びダブルチェックロックとC#
-
27-10-2019 - |
質問
最近、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
}
}
私のポイントは、他のスレッドがもう一度アイテムを初期化しようとしないでください。一方、値が初期化されたら多くのスレッドは同時に読み取る必要があります。アップグレード可能な読み取りロックは、書き込みロックが取得された場合、すべてのリーダーをブロックするはずです。したがって、オブジェクトが初期化されている間、動作はアップグレード可能な読み取りロックが始まるロックステートメントを持つことに似ています。初期化後、アップグレード可能な読み取りロックにより複数のスレッドが許可されるため、各スレッドを待機するパフォーマンスヒットは存在しません。
記事も読みました ここ 揮発性が読み取り前と書き込み後にメモリの障壁を自動的に挿入すると言うので、読み取りと書き込みの間に1つの手動で定義された障壁のみが、_valueReferenceオブジェクトが正しく読み取られるように十分であると仮定します。このアプローチを使用したことに対するあなたのアドバイスと批判に喜んで感謝します。
解決
@mannimarcoが作成するポイントを強調するために:これが値への唯一のアクセスポイントであり、そのように見える場合、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;
}
}
}
他のヒント
警告:単一のスレッドのみが、一度にアップグレードされたupgradeablereadLockモードに入ることができます。チェックアウト readerwriterlockslim. 。したがって、最初のスレッドが書き込みモードに入り、オブジェクトを作成している間にスレッドが積み上げられた場合、バックアップが(できれば)解決されるまでボトルネックができます。静的イニシャルイザーを使用することを真剣に提案します。それはあなたの人生を楽にします。
編集:オブジェクトを再作成する頻度に応じて、実際にモニタークラスとその待機およびパルス方法を使用することをお勧めします。値を再作成する必要がある場合は、スレッドをオブジェクトを待機させ、別のオブジェクトをパルスして、ワーカーが目覚めて新しいオブジェクトを作成する必要があることを知らせるようにします。オブジェクトが作成されると、Pulseallはすべてのリーダースレッドが目覚めて新しい値をつかむことができます。 (理論的には)