Вопрос

Недавно я рефакторировал некоторые из моих C# Code, и я нашел несколько двухпрофильных методов блокировки. Я не знал, что тогда это была плохая практика, и я действительно хочу от нее избавиться.

Проблема в том, что у меня есть класс, который должен быть лениво инициализирован и часто доступен множеством потоков. Я также не хочу перемещать инициализацию на статический инициализатор, потому что я планирую использовать слабую ссылку, чтобы инициализированный объект оставался слишком долго в памяти. Однако, если это необходимо, я хочу «оживить» объект, гарантируя, что это происходит в потоке.

Мне было интересно, использует ли использование readerwriterlockslim в C# и ввести UpgraglereAdlock перед первой проверкой, а затем, если необходимо, введите блокировку записи для инициализации, будет приемлемым решением. Вот что я имею в виду:

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 правильно прочитал. Я с радостью буду признателен за ваши советы и критику за использование этого подхода.

Это было полезно?

Решение

Чтобы подчеркнуть точку зрения @mannimarco: если это единственная точка доступа к значению, и она выглядит таким образом, то вся ваша настройка ReaderWriterLockslim не лучше, чем простой монитор. 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; 
    }
  }    
}

Другие советы

ПРЕДУПРЕЖДЕНИЕ: только один поток может входить в режим обновления за раз. Проверить Readerwriterlockslim. Анкет Поэтому, если потоки накапливаются, пока первый поток входит в режим записи и создает объект, у вас будет шейка для бутылки, пока резервная копия не будет (надеюсь). Я бы серьезно предложил использовать статический инициализатор, это облегчит вашу жизнь.

РЕДАКТИРОВАТЬ: В зависимости от того, как часто объект должен быть воссоздан, я бы на самом деле предложил использовать класс монитора и его методы ожидания и импульса. Если значение необходимо воссоздать, попросите потоки ждать объекта и пульсировать другой объект, чтобы работник знал, что ему нужно проснуться и создать новый объект. Как только объект будет создан, Pulseall позволит всем потокам считывателя проснуться и получить новое значение. (теоретически)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top