Создание множественной переменной syncLock для экземпляра - PullRequest
4 голосов
/ 24 марта 2011

У меня есть два внутренних свойства, которые используют ленивую загрузку вспомогательных полей и используются в многопоточном приложении, поэтому я реализовал схему двойной проверки блокировки согласно этой статье MSDN

Теперь, сначала предполагая, что это подходящий шаблон, во всех примерах показано создание одного объекта блокировки для экземпляра.Если мои два свойства не зависят друг от друга, не будет ли эффективнее создать экземпляр блокировки для каждого свойства?

Мне кажется, что, возможно, существует только одно, чтобы избежать взаимоблокировок или условий гонки.,Очевидная ситуация не приходит в голову, но я уверен, что кто-то может показать мне одну ... (Я не очень опытен с многопоточным кодом, очевидно)

private List<SomeObject1> _someProperty1;
private List<SomeObject2> _someProperty2;
private readonly _syncLockSomeProperty1 = new Object();
private readonly _syncLockSomeProperty2 = new Object();

internal List<SomeObject1> SomeProperty1
{
  get
  {
    if (_someProperty1== null)
    {
      lock (_syncLockSomeProperty1)
      {
        if (_someProperty1 == null)
        {
          _someProperty1 = new List<SomeObject1>();
        }
      }
    }
    return _someProperty1;
  }

  set
  {
    _someProperty1 = value;
  }
}

internal List<SomeObject2> SomeProperty2
{
  get
  {
    if (_someProperty2 == null)
    {
      lock (_syncLockSomeProperty2)
      {
        if (_someProperty2 == null)
        {
          _someProperty2 = new List<SomeObject2>();
        }
      }
    }
    return _someProperty2;
  }

  set
  {
    _someProperty2 = value;
  }
}

Ответы [ 3 ]

3 голосов
/ 24 марта 2011

Если ваши свойства действительно независимы, то использование независимых замков для каждого из них не повредит.

1 голос
/ 24 марта 2011

В случае, если два свойства (или, более конкретно, их инициализаторы) не зависят друг от друга, как в приведенном вами примере кода, имеет смысл иметь два разных объекта блокировки.Однако, когда инициализация происходит редко, эффект будет незначительным.

Обратите внимание, что вы должны также защитить код установщика.Оператор lock накладывает так называемый барьер памяти, который необходим, особенно в многопроцессорных и / или многоядерных системах, для предотвращения гонки.

0 голосов
/ 24 марта 2011

Да, , если они независимы друг от друга, это действительно будет более эффективным, так как доступ к одному не блокирует доступ к другому.Вы также получаете деньги за риск тупика, если эта независимость окажется ложной.

Вопрос в том, предполагая, что _someProperty1 = new List<SomeObject1>(); не является реальным кодом для присвоения _someProperty1 (вряд ли стоит ленивая нагрузка, правда?), тогда возникает вопрос: может ли код, который заполняет SomeProperty1, когда-либо вызывать то, что заполняет SomeProperty2, или наоборот, через любой путь кода, независимо от того, насколько причудливым?1008 * Даже если один может позвонить другому, не может быть тупика, но если они оба могут звонить друг другу (или 1 вызов 2, 2 вызова 3 и 3 вызова 1 и т. Д.), То тупик может определеннослучается.

Как правило, я бы начал с широких блокировок (одна блокировка для всех заблокированных задач), а затем сузил бы блокировки по мере необходимости при оптимизации.В тех случаях, когда у вас есть, скажем, 20 методов, которые требуют блокировки, тогда оценка безопасности может оказаться сложнее (также вы начинаете заполнять память только объектами блокировки).

Обратите внимание, что с вашим кодом есть две проблемытакже:

Во-первых, вы не блокируете свой сеттер.Возможно, это нормально (вы просто хотите, чтобы ваша блокировка предотвращала множественные тяжелые вызовы в метод загрузки, и на самом деле не волнует, есть ли перезаписи между set и get), возможно, это катастрофа.

Во-вторых, в зависимости от того, на каком процессоре он запущен, при проверке дважды проверяйте, могут ли возникать проблемы с переупорядочением чтения / записи, поэтому у вас должно быть поле volatile или вызов барьера памяти.См. http://blogs.msdn.com/b/brada/archive/2004/05/12/130935.aspx

Редактировать:

Также стоит подумать, действительно ли это вообще нужно.

Учтите, что сама операция должна быть поточно-ориентированной:

  1. Сделать кучу вещей сделано.
  2. Создать объект, основанный на этой куче вещей.
  3. Назначить этот объект локальной переменной.

1 и 2 будут происходить только в одном потоке, а 3 атомарно.Таким образом, преимущество блокировки:

  1. Если выполнение вышеуказанных шагов 1 и / или 2 имеет свои проблемы с многопоточностью и не защищено от них своими собственными блокировками, блокировка100% необходимо.

  2. Если что-то будет иметь катастрофические последствия для значения, полученного на шагах 1 и 2, а затем - для повторения шагов 1 и 2,блокировка необходима на 100%.

  3. Блокировка предотвратит многократное использование отходов 1 и 2.

Итак, если мы можем правитьВ случае проблем 1 и 2 (требуется небольшой анализ, но это часто возможно), тогда мы можем предотвратить бесполезные потери в случае 3.Теперь, может быть, это большое беспокойство.Однако, если он возникнет редко, а также не будет такой большой тратой, когда он это сделал, выигрыш от отсутствия блокировки перевесит выигрыш от блокировки.

Если сомневаетесь, блокировка, вероятно, безопаснееподход, но возможно, что лучше просто жить с потраченной впустую операцией.

...