Pull to refresh

Comments 35

public class MyClass
{
  private static readonly Lazy<MyClass> instance = new Lazy<MyClass>(() => new MyClass());
  private MyClass(){}
  public static MyClass Instance
  {
    get { return instance.Value; }
  }
}

Самый понятный подход, в отличии от надежды на какой-то спецэффект IL-генерации.
Совершенно согласен, но не каждого разработчика радует наличие 4-го фреймворка. Многие все еще сидят на предыдущих версиях.
Ну Lazy не настолько сложен, его можно и руками набросать, если проект не будет использовать .NET F 4
Самостоятельная реализация Lazy этот подход делает уже не столь привлекательным, ИМХО. Хотя, конечно, можно просто взять исходники фреймворка и перетянуть его в свою библиотеку.
Классная идея.

Я обычно что-то такое использовал:
public class SomeClass
{
  private SomeClass _instance;
  private SomeClass() {}

  private SomeClass Instance
  {
    if (_instance == null)
      _instance = new SomeClass();
    return _instance;
  }
}
ну, этот код не совсем потокобезопасный.
Если два потока одновременно попытаются в первый раз достучаться к инстансу, у нас получится два объекта.

З.Ы. Пару лет назад я отладивал багу, которая была из-за непотокобезопасного синглтона. Так что я бы дважды подумал, прежде чем использовать подобный код.
Согласен полностью.

Теперь у меня просто один singleton, в котором все остальные сидят. И я контролирую его создание.
Это для игры такое дело, есть singleton GameEngine, который содержит в себе уже инстансы на различные сервисы: TextureManager, SoundManager, UiSystem, TriggerService и т.д.
клево, конечно, но как вы ЭТО могли использовать?! Я так думаю вы static и public забыли. Это минимально.
А еще (даже если будет static public SomeClass Instance) у вас это не потоко безопасно. Вам же написали вконце: надо делать double lock в такой реализации…
Я не понял, где и что я забыл. У меня есть класс Service, с открытым статическим свойством типа Service. Обращение к этому свойство абсолютно потокобезопасно, поскольку CLR гарантирует только один вызов статического конструктора.

> Вам же написаи вконце: надо делать double lock в такой реализации…
В какой реализации? Еще раз напомню, с потокобезопаснотью класса Service тотальное ОК.
Сергей, не нервничайте. jonie комментировал не ваш код ;-)
Упс! Сори. Не всегда тут понятно, кто на что отвечает:(
Да забыл :)
Даешь Resharper встроенный на хабре.
Стандартный пример синглтона вроде бы даже с msdn/
public sealed class Singletone
{
private static Singletone instance;
private object sync = new Object();

public static Singletone GetInstance()
{
lock(sync)
{
if(instance == null)
instance = new Singletote()
}
return instance;
}

}
Если честно, то msdn — это далеко не самый лучший источник информации по правильному применению идиом, паттернов, да и качество кода, там зачастую на букву г.

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

Именно поэтому более предпочтительным вариантом является следующий:
public static Singleton GetInstance()
{
// Если это условие не выполнится, то никакие блокировки не нужны, поскольку
// экземпляр синглтона уже создан
if ( instance == null )
{
// Но поскольку никаких блокировок сделано не было, есть вероятность,
// что произошло переключение контекста и другой поток успел создать
// экземпляр синглтона, поэтому захватываем блокировку и проверяем
// наличие экземпляра еще раз
lock(sync)
{
if (instance == null)
{
// Да, экземпляр таки не создался, давайте-ка создадим его
instance = new Singleton();
}
}
}
return instance;
}


З.Ы. За подробностями прошу к Скиту: Implementing the Singleton Pattern in C#.
Не… в MSDN предпочитают перестраховаться и сделать так:

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;
}
}
}
Познавательно

Но я так и не понял почему во втором случае «метод Create подвисает на веки». Можно пояснить?
Воспроизводится это дело с полпинка, а вот понять, где именно там лочится, пока так и не понял.
public static Service instance;
public static Service Instance {

get {
if(instance==null)
instance = new Service();

return instance;

}

}

так, не?
уппс, меня опередили, пока писал. :(
Ну и ответ тот же. Код не потокобезопасен;)
В любом случае, спасибо за статью, особо Singletone я пока не пользовался, но в будущем где-то придется.
К этому паттерну вообще, нужно относиться с осторожностью. Именно в том проекте, о котором вскользь идеть речь в статье, можно было бы избавиться как минимум от половины синглтонов.

В большинстве случаев, это скорее антипаттерн, нежели паттерн. Поскольку синглтон — есть глобальная переменная, а они (эти глобальные переменные) являются не самым лучшим подходом проектирования. С ними аккуратнее нужно быть:)
Ах Рихтер, редиска, забыл про необходимость статического конструктора упомянуть! (в его книге написано, что простой статик филд эквивалентен способу с двойной блокировкой).
Еще раз напомню, что статик филд с точки зрения потокобезопасности и так эквивалентен двойной блокировке. Проблемы возможны не с многопоточностью, а со временем создания экземпляра: он может быть создан не совсем тогда, когда вы этого ожидаете.

З.Ы. Рихтер в поряде, у него есть неплохой раздел по поводу флага beforefieldinit (хотя семантика названия описана у него не совсем точно, и, кажись, не сказано о том, что это злой implementation detail).
А для чего нужен ленивый синглтон? Обычно он описывается небольшой структурой, и как бы непонятно зачем экономить? Если ж подобных этому классу — их тыщи и миллионы, то тогда другой вопрос — зачем столько синглтонов? Непонятно…
У ленивости есть несколько плюсов:
1. Ресурс может вообще не понадобиться, а мы его создали. Синглтон может быть и тяжеловесной хренью.
2. Недетерменированное создание ресурса чревато. Если конструктор синглтона вдруг может падать, то это падение будет происходить в совершенно непонятных местах. В случае же ленивого обращения, нам хотя бы будет понятна причина и место возникновение ошибки.
Вот я про то же: вроде бы может быть тяжеловесной хренью, но вот так вот чтобы придумать сразу на гора десяток различных примеров… — у меня и одного не получается. Все, что приходит в голову, является примером объектов несколько другого рода, и тогда Singleton — это вроде как неуместное применение паттерна. Расскажите о примере боевого использования тяжелого синглтона, чтоб отмести все подозрения?
Я привел пример, когда отсутствие ленивой инициализации приводило к дедлоку приложения. Ну и меня больше беспокоит не пустая трата ресурсов, что плохо, но не смертельно, а недерменированность исключений, когда аппликуха начинает падать и ты понятия не имеешь, в каком из 20 синглтонов произошла ошибка.
Например, настройки приложения могут быть довольно большие, а конкретный сеанс к ним может и не обращаться.
null, undefined, none, void — то что приходит на ум в виде примеров. Настройки — уже не настоль хороший пример, поскольку настройки могут прочитаться, или нет: из файла или иным путем. А синглтон должен существовать более-менее неизменным на протяжении не только всей жизни процесса. А если он ленивый? Код, описывающий его инициализацию может занять больше места, чем то место, на которое ссылается указатель. Если конфигурация должна быть одна на весь процесс — пожайлуста. Должен ли это быть синглтон — совершенно не обязательно. Ведь из чего исходят, задумывая конфигурацию синглтоном: нужна гарантия единственности? А как насчет других гарантий, что дают юнит тесты, и с которыми синглтоны не очень дружны по своей природе? Конечно дело вкуса, но вопрос остается открытым — наверное есть действительно какие-то примеры использования синглтона, если этому посвящено столько статей на хабре — однозначно должно быть парочка значимых, там, где критерий единственности был бы настолько потрясающим решением задачи, что прям — «вау». Я просто не вижу — откройте мне веки.
Как вывод, синглтон на статических классах вполне себе работоспособен, если приучить себя не злоупотреблять инициализаторами в объявлениях, а всегда выносить их в статический конструктор.

Да и для обычных, не статических классов этот совет, кажется, тоже может быть актуален.
Какой-то у вас синглтон недоделанный…

if (args[0] == "--console")
Service.Instance.Start(); // True singleton
else
ServiceBase.Run(new Service()); // Совсем уже и не синглтон
Сори, код немного не правильный. На самом деле должно быть:
if (args[0] == "--console")
Server.Instance.Start();
else
ServiceBase.Run(new Service());

А внутри обработчика OnStart сервиса снова вызывается метод Server.Instance.Start();
Что-то я недопонял как указание явного статичекого конструктора решает проблему в целом. Получается что изменился момент инициализации статического поля, но вы же сами пишите:
Поскольку статический конструктор указанного типа должен вызываться не более одного раза в домене приложения, то CLR вызывает его внутри некоторой блокировки. Тогда, если поток, исполняющий статический конструктор будет ожидать завершения другого потока, который в свою очередь попытается захватить ту же самую внутреннюю блокировку CLR, мы получим классический дедлок.
Т.е. дедлок по прежнему возможен. Имхо стоит все таки разобраться почему именно виснет Create.
Да, дедлок возможен, но в данном конкретном случае он происходил именно при вызове этого дела до функции Main и не происходил, если он вызывался при первом обращении внутри функции Main.

Инициализация счетчиков производительности в статическом конструкторе все ще может привести к дедлоку, поэтому наиболее оптимальным решением является переход на double ckecked lock (или на Lazy).
Sign up to leave a comment.

Articles