Недавно я проводил рефакторинг некоторых моих кодов на C # и обнаружил несколько проверенных методов блокировки. Тогда я не знал, что это плохая практика, и я действительно хочу от нее избавиться.
Проблема в том, что у меня есть класс, который должен быть лениво инициализирован и часто доступен множеству потоков. Я также не хочу перемещать инициализацию в статический инициализатор, потому что я планирую использовать слабую ссылку, чтобы инициализированный объект не оставался в памяти слишком долго. Однако при необходимости я хочу «оживить» объект, гарантируя, что это произойдет в поточно-ориентированном режиме.
Мне было интересно, будет ли приемлемым решением использовать ReaderWriterLockSlim в C # и ввести 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
}
}
Моя точка зрения заключается в том, что никакой другой поток не должен пытаться инициализировать элемент еще раз, в то время как многие потоки должны читать одновременно после инициализации значения. Обновляемая блокировка чтения должна блокировать все считыватели, если блокировка записи получена, поэтому, пока объект инициализируется, поведение будет аналогично наличию оператора блокировки, где начинается обновляемая блокировка чтения. После инициализации обновляемая блокировка чтения разрешит несколько потоков, поэтому прирост производительности при ожидании каждого потока не будет присутствовать.
Я также прочитал статью здесь , в которой говорится, что энергозависимость приводит к тому, что барьеры памяти автоматически вставляются перед чтением и после записи, поэтому я предполагаю, что для проверки будет достаточно только одного определенного вручную барьера между чтением и записью что объект _valueReference правильно прочитан. Буду рад вашим советам и критике за использование этого подхода.