Почему эта двойная проверка правильности блокировки?(.СЕТЬ) - PullRequest
7 голосов
/ 02 декабря 2011

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

Я читал эту статьюДжо Даффи о реализации синглтона с двойной проверкой блокировки: http://www.bluebytesoftware.com/blog/PermaLink,guid,543d89ad-8d57-4a51-b7c9-a821e3992bf6.aspx

И (вариант) решения, которое он, казалось, предложил, это:

class Singleton {
private static object slock = new object();
private static Singleton instance;
private static int initialized;
private Singleton() {}
public Instance {
    get {
        if (Thread.VolatileRead(ref initialized) == 0) {
            lock (slock) {
                if (initialized == 0) {
                    instance = new Singleton();
                    initialized = 1;
                }
            }
        }
        return instance;
    }
}

}

Мой вопрос: разве не существует опасность переупорядочения записей?В частности, эти две строки:

instance = new Singleton();
initialized = 1;

Если эти записи инвертированы, то какой-то другой поток все еще может читать ноль.

Ответы [ 3 ]

5 голосов
/ 02 декабря 2011

Я думаю, что ключ находится в связанной статье (http://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S5). В частности, что реализованная в MS модель памяти .NET 2.0 имеет следующее свойство:

Запись не может проходить мимо других записей из того же потока.

Даффи упоминает, что была проделана большая работа для поддержки этого на IA-64:

Мы достигаем этого, гарантируя, что записи имеют семантику 'release' на IA-64 с помощью инструкции st.rel. Один st.rel x гарантирует, что любые другие загрузки и сохранения, ведущие к его выполнению (в потоке физических команд), должны были появиться в каждом логическом процессоре, по крайней мере, к тому времени, когда новое значение x станет видимым для другого логического процессора. Для загрузок может быть задана семантика «получения» (с помощью инструкции ld.acq), что означает, что любые другие загрузки и сохранения, которые происходят после ld.acq x, не могут появиться до загрузки.

Обратите внимание, что Даффи также упоминает, что это гарантия для MS - она ​​не является частью спецификации ECMA (по крайней мере, на момент написания статьи в 2006 году). Так что Моно может быть не так хорош.

1 голос
/ 05 декабря 2011

Первоначальные комментарии

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

Типы значений, очевидно, не могут хранить значения null, поэтому необходимо использовать другую переменную для оповещения о завершении инициализации. Автор упоминает все это, а затем смущает разговоры о чтении instance как null. Предположительно, автор думал о действительно наивном разработчике, который однажды неправильно использовал этот вариант для типов значений, а затем продолжал применять его, также неправильно, для ссылочных типов. В случае типа значения поток может прочитать и использовать struct с инициализацией поля по умолчанию, когда это не было предназначено. В случае ссылочных типов поток может читать и использовать null экземпляр.

Использование Thread.VolatileRead было предложено автором для исправления этой вариации. Без изменчивого чтения чтение instance в операторе возврата может быть отменено до чтения initialized, как это.

class Singleton 
{
  private static object slock = new object();
  private static Singleton instance;
  private static int initialized;
  private Singleton() {}

  public Instance {
    get {
        var local = instance;
        if (initialized == 0) {
            lock (slock) {
                if (initialized == 0) {
                    instance = new Singleton();
                    initialized = 1;
                }
            }
        }
        return local;
    }
  }
}

Надеюсь, приведенное выше изменение порядка кода наглядно демонстрирует проблему. И столь же очевидно, что изменчивое чтение initialized предотвращает снятие чтения instance.

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

Ответы на ваши вопросы

У меня такой вопрос, разве не существует опасность того, что заказана?

ДА (квалифицировано): Как вы правильно указали, записи в instance и initialized можно поменять местами внутри lock. Хуже того, записи, которые могут происходить внутри Singleton.ctor, также могут происходить не по порядку, так что instance назначается до , когда экземпляр полностью инициализируется. Другой поток может увидеть instance set, но этот экземпляр может быть в частично сконструированном состоянии.

Однако записи в реализации Microsoft CLI имеют семантику ограничения выпуска. То есть все, что я только что сказал, неприменимо при использовании среды выполнения .NET Framework на любой аппаратной платформе. Но такая непонятная среда, как Mono, работающая на ARM , может демонстрировать проблемное поведение.

Использование автором Thread.VolatileRead для «исправления» этого варианта в общем случае не сработает, поскольку оно не решает проблему переупорядоченных записей. Код не является на 100% переносимым. Это одна из причин, почему я сомневаюсь, что автор предлагал этот вариант.

Каноническая вариация использования одной переменной instance в сочетании с volatile, очевидно, является правильным решением. Ключевое слово volatile имеет семантику acqu-fence при чтениях и семантику release-fence при записи, поэтому оно решает обе проблемы; тот, который вы определили, и тот, к которому относится статья.

0 голосов
/ 02 декабря 2011

Согласно http://msdn.microsoft.com/en-us/library/ee817670.aspx синглтон типа

// .NET Singleton
sealed class Singleton 
{
    private Singleton() {}
    public static readonly Singleton Instance = new Singleton();
}

гарантированно безопасен для потоков

Платформа внутренне гарантирует безопасность потоков при статической инициализации типа. [..] В самой платформе есть несколько классов, которые используют этот тип синглтона, хотя вместо этого используется имя свойства. Концепция точно такая же.

...