Простая реализация Singleton - PullRequest
8 голосов
/ 13 мая 2011

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


public class Singleton
{
    private static Singleton _instance;
    private Singleton() { Console.WriteLine("Instance created"); }

    public static Singleton Instance
    {
        get
        {
            if (_instance == null)
            {
                Interlocked.CompareExchange(ref _instance, new Singleton(), null);
            }
            return _instance;
        }
    }
    public void DoStuff() { }
}

РЕДАКТИРОВАТЬ: тест на безопасность потока не удалось, кто-нибудь может объяснить, почему?Как получается, что Interlocked.CompareExchange не является по-настоящему атомарным?


public class Program
{
   static void Main(string[] args)
   {
      Parallel.For(0, 1000000, delegate(int i) { Singleton.Instance.DoStuff(); });
   }
} 

Result (4 cores, 4 logical processors)
Instance created
Instance created
Instance created
Instance created
Instance created

Ответы [ 13 ]

10 голосов
/ 13 мая 2011

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

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

  public static Singleton Instance{get{return instance;}}
}

Абсолютно поточно-ориентированный в отношении инициализации.

Изменить: в случае, если я не был понятен, ваш код ужасно неправильно . И проверка if, и проверка new не поточно-ориентированы! Вам нужно использовать подходящий класс синглтона.

8 голосов
/ 16 февраля 2012

Возможно, вы создаете несколько экземпляров, но они будут собирать мусор, потому что они нигде не используются.Ни в коем случае переменная поля static _instance не изменяет свое значение более одного раза, за один раз, когда оно переходит от нуля к действительному значению.Следовательно, потребители этого кода будут видеть только один и тот же экземпляр, несмотря на то, что было создано несколько экземпляров.

Блокировка свободного программирования

Джо Даффи в своей книге под названием Параллельное программирование в Windows на самом деле анализирует тот самый паттерн, которыйвы пытаетесь использовать в главе 10 «Модели памяти и Lock Freedom», стр. 526.

Он называет этот шаблон ленивой инициализацией расслабленной ссылки:

public class LazyInitRelaxedRef<T> where T : class
{
    private volatile T m_value;
    private Func<T> m_factory;

    public LazyInitRelaxedRef(Func<T> factory) { m_factory = factory; }


    public T Value
    {
        get
        {
            if (m_value == null) 
              Interlocked.CompareExchange(ref m_value, m_factory(), null);
            return m_value;
        }
    }

    /// <summary>
    /// An alternative version of the above Value accessor that disposes
    /// of garbage if it loses the race to publish a new value.  (Page 527.)
    /// </summary>
    public T ValueWithDisposalOfGarbage
    {
        get
        {
            if (m_value == null)
            {
                T obj = m_factory();
                if (Interlocked.CompareExchange(ref m_value, obj, null) != null && obj is IDisposable)
                    ((IDisposable)obj).Dispose();
            }
            return m_value;
        }
    }
}

Как мы можемПосмотрите, в приведенном выше примере методы блокируются бесплатно за счет создания одноразовых объектов.В любом случае свойство Value не изменится для потребителей такого API.

Балансировка компромиссов

Lock Freedom имеет свою цену и требует тщательного выбора компромиссов.В этом случае цена свободы блокировки заключается в том, что вы должны создавать экземпляры объектов, которые вы не собираетесь использовать.Это может быть приемлемой ценой для оплаты, поскольку вы знаете, что, будучи свободными от блокировки, снижается риск возникновения взаимоблокировок, а также конкуренции за потоки.

В этом конкретном экземпляре, однако, семантика синглтона по сути является созданием одиночного экземпляра объекта, поэтому я бы предпочел выбрать Lazy<T>, как @Centro цитирует в своем ответе.

Тем не менее, все еще напрашивается вопрос, когда должен использовать Interlocked.CompareExchange?Мне понравился ваш пример, он довольно провокационен, и многие люди очень быстро опровергают его как неправильный, если он не ужасно неправильный, как цитаты @Blindy.

Все сводится к тому, рассчитали ли вы компромиссы и решили:

  • Насколько важно, чтобы вы создали один и только один экземпляр?
  • Насколько важно быть свободным от блокировки?

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

6 голосов
/ 14 мая 2011

Чтобы не использовать «двойную проверку блокировки мамбо-джамбо» или просто не реализовывать собственный синглтон, заново изобретающий колесо, используйте готовое решение, включенное в .NET 4.0 - Lazy .

3 голосов
/ 13 мая 2011

что по этому поводу?

public sealed class Singleton
{
    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return Nested.instance;
        }
    }

    class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly Singleton instance = new Singleton();
    }
}

Это пятая версия на этой странице: http://www.yoda.arachsys.com/csharp/singleton.html

Я не уверен, но, похоже, автор считает, что это и потокобезопасная, и ленивая загрузка.

3 голосов
/ 13 мая 2011

Я не уверен, что вы можете полностью доверять этому. Да, Interlocked.CompareExchanger является атомарным, но новый Singleton () не будет атомарным в любом нетривиальном случае. Так как он должен быть оценен перед обменом значениями, это вообще не будет поточно-ориентированная реализация.

3 голосов
/ 13 мая 2011
public class Singleton
{
    private static Singleton _instance = new Singleton();
    private Singleton() {}

    public static Singleton Instance
    {
        get
        {
            return _instance;
        }
    }
}
2 голосов
/ 01 августа 2013

Ваш одиночный инициализатор ведет себя точно так, как и должен.См. алгоритмы без блокировок Рэймонда Чена: Конструктор-одиночка :

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

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

Каждый поток создает объект;поскольку он думает, что никто еще не создал это.Но тогда во время InterlockedCompareExchange только один поток действительно сможет установить глобальный синглтон.

бонусное чтение

  • Одноразовая инициализация Вспомогательные функции избавляют вас от необходимости писать весь этот код самостоятельно.Они решают все проблемы с синхронизацией и барьером памяти и поддерживают как модели "один человек получает инициализацию", так и модели "бесплатно для всех инициализации".
  • Примитив для отложенной инициализации.NET предоставляет ту же версию на C #.
1 голос
/ 18 ноября 2015

Автоматическая инициализация свойств (C # 6.0), по-видимому, не вызывает множественных экземпляров Singleton, которые вы видите.

public class Singleton
{    
    static public Singleton Instance { get; } = new Singleton();
    private Singleton();
}
1 голос
/ 16 мая 2011
1 голос
/ 13 мая 2011

У вас все еще есть проблема, которую вы вполне возможно создаете и выбрасываете экземпляры своего синглтона. При выполнении Interlocked.CompareExchange() конструктор Singleton всегда будет выполняться независимо от того, будет ли присвоение выполнено успешно. Таким образом, вы не в лучшем положении (или хуже, ИМХО), чем если бы вы сказали:

if ( _instance == null )
{
  lock(latch)
  {
    _instance = new Singleton() ;
  }
}

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

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