it-swarm.com.ru

Безопасный поток C # Singleton Pattern

У меня есть несколько вопросов относительно шаблона синглтона, как описано здесь: http://msdn.Microsoft.com/en-us/library/ff650316.aspx

Следующий код является выдержкой из статьи:

using System;

public sealed class Singleton
{
   private static volatile Singleton instance;
   private static object syncRoot = new object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         if (instance == null) 
         {
            lock (syncRoot) 
            {
               if (instance == null) 
                  instance = new Singleton();
            }
         }

         return instance;
      }
   }
}

В частности, в приведенном выше примере, есть ли необходимость дважды сравнивать экземпляр с нулем, до и после блокировки? Это необходимо? Почему бы сначала не выполнить блокировку и сделать сравнение?

Есть ли проблема в упрощении следующего?

   public static Singleton Instance
   {
      get 
      {
        lock (syncRoot) 
        {
           if (instance == null) 
              instance = new Singleton();
        }

         return instance;
      }
   }

Дорогое выполнение блокировки?

65
Wayne Phipps

Выполнение блокировки ужасно дорого по сравнению с простой проверкой указателя instance != null.

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

Подумайте об этом так: простая проверка null (без lock) гарантированно даст вам правильный пригодный для использования ответ только тогда, когда этот ответ «да, объект уже построен». Но если ответ «еще не создан», то у вас недостаточно информации, потому что вы действительно хотели бы знать, что он «еще не создан и ни один другой поток не собирается создавать его в ближайшее время». Таким образом, вы используете внешнюю проверку в качестве очень быстрого начального теста и запускаете правильную, безошибочную, но «дорогую» процедуру (блокируйте, затем проверяйте), только если ответ «нет».

Вышеприведенная реализация достаточно хороша для большинства случаев, но на данный момент неплохо бы пойти и прочитать статью Джона Скита о синглетонах в C # , в которой также оцениваются другие альтернативы.

107
Jon

Ленивая версия:

public sealed class Singleton
{
    static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton());
    private Singleton() { }

    public static Singleton Instance => lazy.Value;
}

Требуется .NET 4 и C # 6.0 (VS2015) или новее.

22
andasa

Выполнение блокировки: довольно дешево (все же дороже, чем нулевой тест).

Выполнение блокировки, когда ее имеет другой поток: вы получаете стоимость того, что им еще предстоит сделать во время блокировки, добавленную к вашему собственному времени.

Выполнение блокировки, когда ее имеет другой поток, и десятки других потоков также ожидают ее: Калека.

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

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

(Между прочим, если вы можете просто использовать private static volatile Singleton instance = new Singleton() или если вы можете просто не использовать синглтоны, а вместо этого использовать статический класс, оба лучше в отношении этих проблем).

9
Jon Hanna

Причина в производительности. Если instance != null (который всегда будет иметь место, кроме самого первого раза), нет необходимости делать дорогостоящую lock: два потока, одновременно обращающиеся к инициализированному синглтону, будут синхронизированы без необходимости.

6
Heinzi

Почти в каждом случае (то есть во всех случаях, кроме самых первых), instance не будет нулевым. Получение блокировки обходится дороже, чем простая проверка, поэтому проверка значения instance перед блокировкой - хорошая и бесплатная оптимизация.

Этот шаблон называется двойной проверкой блокировки: http://en.wikipedia.org/wiki/Double-checked_locking

4
Kevin Gosse

Джеффри Рихтер рекомендует следующее:



    public sealed class Singleton
    {
        private static readonly Object s_lock = new Object();
        private static Singleton instance = null;

        private Singleton()
        {
        }

        public static Singleton Instance
        {
            get
            {
                if(instance != null) return instance;
                Monitor.Enter(s_lock);
                Singleton temp = new Singleton();
                Interlocked.Exchange(ref instance, temp);
                Monitor.Exit(s_lock);
                return instance;
            }
        }
    }

3
Yauheni Charniauski

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

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();

    private Singleton() { }

    public static Singleton Instance()
    {
        return instance;
    }
}
0
Brian Ogden