Подтвердить что ты не робот

Thread Safe С# 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;
      }
   }

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

    public static Singleton Instance => lazy.Value;
}

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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



    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;
            }
        }
    }

Ответ 7

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

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

    private Singleton() { }

    public static Singleton Instance()
    {
        return instance;
    }
}

Ответ 8

Другая версия Singleton, где следующая строка кода создает экземпляр Singleton во время запуска приложения.

private static readonly Singleton singleInstance = new Singleton();

Здесь CLR (Common Language Runtime) позаботится об инициализации объекта и безопасности потока. Это означает, что нам не потребуется явно писать какой-либо код для обработки безопасности потоков в многопоточной среде.

"Стремительная загрузка в одноэлементном шаблоне проектирования - это не процесс, в котором нам нужно инициализировать одноэлементный объект во время запуска приложения, а не по требованию, и держать его готовым в памяти для использования в будущем".

public sealed class Singleton
    {
        private static int counter = 0;
        private Singleton()
        {
            counter++;
            Console.WriteLine("Counter Value " + counter.ToString());
        }
        private static readonly Singleton singleInstance = new Singleton(); 

        public static Singleton GetInstance
        {
            get
            {
                return singleInstance;
            }
        }
        public void PrintDetails(string message)
        {
            Console.WriteLine(message);
        }
    }

от основного:

static void Main(string[] args)
        {
            Parallel.Invoke(
                () => PrintTeacherDetails(),
                () => PrintStudentdetails()
                );
            Console.ReadLine();
        }
        private static void PrintTeacherDetails()
        {
            Singleton fromTeacher = Singleton.GetInstance;
            fromTeacher.PrintDetails("From Teacher");
        }
        private static void PrintStudentdetails()
        {
            Singleton fromStudent = Singleton.GetInstance;
            fromStudent.PrintDetails("From Student");
        }

Ответ 9

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

public sealed class Singleton
{
    private static readonly object Instancelock = new object();
    private Singleton()
    {
    }
    private static Singleton instance = null;

    public static Singleton GetInstance
    {
        get
        {
            if (instance == null)
            {
                lock (Instancelock)
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}