Другими словами, является ли эта реализация Singleton потокобезопасной:
public class Singleton
{
private static Singleton instance;
private Singleton() { }
static Singleton()
{
instance = new Singleton();
}
public static Singleton Instance
{
get { return instance; }
}
}
@JeppeStigNielsen Другие темы не заблокированы. По собственному опыту я получал из-за этого неприятные ошибки. Гарантия заключается в том, что только первый поток запустит статический инициализатор или конструктор, но тогда другие потоки попытаются использовать статический метод, даже если процесс построения не завершился.
@Narvalex Эта программа-пример (источник, закодированный в URL) не может воспроизвести описанную вами проблему. Может это зависит от того, какая у вас версия CLR?
@JeppeStigNielsen Спасибо, что нашли время. Не могли бы вы объяснить мне, почему поле здесь переопределено?
@Narvalex С этим кодом X в верхнем регистре становится -1даже без заправки. Это не проблема безопасности потоков. Вместо этого инициализатор x = -1 запускается первым (он находится в более ранней строке кода, под нижним номером строки). Затем запускается инициализатор X = GetX(), который делает X в верхнем регистре равным -1. Затем запускается «явный» статический конструктор, инициализатор типа static C() { ... }, который изменяет только строчные x. Итак, после всего этого, метод Main (или метод Other) может продолжить чтение X в верхнем регистре. Его значение будет -1, даже с одним потоком.





Спецификация инфраструктуры общего языка гарантирует, что «инициализатор типа должен запускаться ровно один раз для любого заданного типа, если он явно не вызван кодом пользователя». (Раздел 9.5.3.1.) Итак, если у вас нет какой-нибудь дурацкой IL для свободного вызова Singleton ::. Cctor напрямую (маловероятно), ваш статический конструктор будет запускаться ровно один раз перед использованием типа Singleton, будет создан только один экземпляр Singleton, и ваше свойство Instance является потокобезопасным.
Обратите внимание, что если конструктор Singleton обращается к свойству Instance (даже косвенно), тогда свойство Instance будет иметь значение null. Лучшее, что вы можете сделать, - это определить, когда это происходит, и выбросить исключение, проверив, что экземпляр не равен нулю в методе доступа к свойству. После того, как ваш статический конструктор завершит работу, свойство Instance будет отличным от NULL.
Как указывает Зумба ответ, вам нужно будет сделать Singleton безопасным для доступа из нескольких потоков или реализовать механизм блокировки с использованием экземпляра singleton.
Статические конструкторы гарантированно запускаются только один раз для каждого домена приложения, прежде чем будут созданы какие-либо экземпляры класса или будет осуществлен доступ к статическим членам. https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors
Показанная реализация является потокобезопасной для начальной конструкции, то есть для создания объекта Singleton не требуется блокировка или проверка на null. Однако это не означает, что любое использование экземпляра будет синхронизировано. Это можно сделать разными способами; Я показал один ниже.
public class Singleton
{
private static Singleton instance;
// Added a static mutex for synchronising use of instance.
private static System.Threading.Mutex mutex;
private Singleton() { }
static Singleton()
{
instance = new Singleton();
mutex = new System.Threading.Mutex();
}
public static Singleton Acquire()
{
mutex.WaitOne();
return instance;
}
// Each call to Acquire() requires a call to Release()
public static void Release()
{
mutex.ReleaseMutex();
}
}
Обратите внимание: если ваш одноэлементный объект неизменен, использование мьютекса или любого механизма синхронизации является излишним и не должно использоваться. Кроме того, я считаю приведенный выше пример реализации чрезвычайно хрупким :-). Ожидается, что весь код, использующий Singleton.Acquire (), будет вызывать Singleton.Release (), когда это делается с использованием экземпляра singleton. Если этого не сделать (например, преждевременно вернуться, выйти из области действия через исключение, забыть вызвать Release), в следующий раз, когда к этому синглтону будет обращаться из другого потока, он зайдет в тупик в Singleton.Acquire ().
Согласен, хотя пойду и дальше. Если ваш синглтон неизменяемый, использование синглтона излишне. Просто определите константы. В конечном счете, правильное использование синглтона требует, чтобы разработчики знали, что они делают. Какой бы хрупкой ни была эта реализация, она все же лучше, чем та, в которой эти ошибки проявляются случайным образом, а не в виде явно невыпущенного мьютекса.
Один из способов уменьшить хрупкость метода Release () - использовать другой класс с IDisposable в качестве обработчика синхронизации. Когда вы приобретаете синглтон, вы получаете обработчик и можете поместить код, требующий синглтона, в блок using для обработки выпуска.
Для других, кого это может сбить с толку: инициализируются все члены статического поля с инициализаторами. перед - вызывается статический конструктор.
Простой потокобезопасный одноэлементный бланк для повседневного использования: ilyatereschuk.blogspot.com/2013/12/…
@ user2604650 В этом нет ничего поточно-ориентированного, кроме создания экземпляра синглтона, который уже был бы, если бы он был инициализирован в статическом конструкторе. Лично я думаю, что использование такого шаблона для синглтона было бы излишним, поскольку в большинстве случаев единственный раз, когда вы обращаетесь к синглтону, будет во время доступа к свойству статического экземпляра, что в результате является временем вызова статического конструктора.
В наши дни ответ - использовать Lazy<T> - любой, кто использует код, который я изначально опубликовал, делает это неправильно (и, честно говоря, это было не так хорошо с самого начала - 5 лет назад - я был не так хорош в этом деле, как ток-меня есть :)).
Зачем использовать здесь Mutex? Метод Acquire просто возвращает ссылку на экземпляр (это ссылочный тип). Не имеет значения, является ли объект, на который имеется ссылка, изменяемым или неизменным. Ссылка относится к одному и тому же объекту независимо от того, мутирует ли объект. Зачем делать блокировку? Даже если бы экземпляр не был «одноэлементным» или «только для чтения», т.е. даже если бы ссылка на поле могла измениться, начиная с ссылочные присвоения являются атомарными, все равно не было бы необходимости в блокировке / Mutex. Я проголосую против этого ответа @ +109 чистых голосов за ...
@JeppeStigNielsen Идея состоит в том, что вы сохраняете мьютекс, пока используете объект, а затем отпускаете его, когда закончите. Опять же, есть более эффективные способы сделать это в зависимости от того, сколько накладных расходов вы хотите возложить на своих пользователей. Сейчас я больше поклонник шаблона IDisposable / using (), но концепция та же.
@Zooba нет ничего, что могло бы помешать вызывающему абоненту сохранить ссылку на Singleton после вызова Release: var s = Singleton.Acquire(); Singleton.Release(); s.Property = value;
@phoog Да, плохие парни все равно могут победить, если они могут запустить произвольный код в вашем приложении. Если вы не доверяете своим коллегам-разработчикам писать хороший код, у вас возникают проблемы, которые синглтон не может решить.
@Zooba Я не доверяю себе писать хороший код, поэтому стараюсь избегать требований вроде «если вы позвоните так, то вам придется вызвать позже» или «вы не сможете использовать это после того, как позвоните тому». Я не беспокоюсь о плохих парнях, просто о человеческих ошибках. Конечно, тестирование должно уловить это, но зачем полагаться на тестирование, если почти всегда можно создать более чистый дизайн, менее уязвимый для человеческих ошибок?
@phoog Справедливо, но я бы отнес "синглтоны, применяемые компилятором" к категории вещей, требующих более чистого дизайна. Создание более строго принудительного синглтона не поможет вам, когда проблема в том, что у вас не должно быть синглтона. (Неудивительно, что мои взгляды изменились за 7 лет с тех пор, как я написал этот ответ, не то чтобы я когда-либо считал это действительно хорошей идеей :))
@Zooba именно так. Я не учел мудрости (или ее отсутствия) общего дизайна.
@ AdamW.McKinley - обратите внимание, что порядок статической инициализации отличается в версиях до .NET 4.0 и 4.0. codeblog.jonskeet.uk/2010/01/26/…
@Zooba Не могли бы вы пояснить, что означает «запускать только один раз на домен приложения»? Что произойдет, если я попытаюсь вызвать это из своего приложения службы Windows, а также из веб-приложения?
Использование статического конструктора на самом деле является threadsafe. Статический конструктор гарантированно будет выполнен только один раз.
The static constructor for a class executes at most once in a given application domain. The execution of a static constructor is triggered by the first of the following events to occur within an application domain:
- An instance of the class is created.
- Any of the static members of the class are referenced.
Так что да, вы можете быть уверены, что ваш синглтон будет правильно создан.
Zooba сделал отличную мысль (и за 15 секунд до меня тоже!), Что статический конструктор не гарантирует поточно-безопасный общий доступ к синглтону. С этим нужно будет поступить по-другому.
Статические конструкторы гарантированно срабатывают только один раз для каждого домена приложения, поэтому ваш подход должен быть в порядке. Однако функционально он ничем не отличается от более сжатой, встроенной версии:
private static readonly Singleton instance = new Singleton();
Безопасность потоков становится более серьезной проблемой, когда вы лениво инициализируете что-то.
Эндрю, это не полностью эквивалентно. Если не использовать статический конструктор, теряются некоторые гарантии того, когда инициализатор будет выполнен. Пожалуйста, просмотрите эти ссылки для более подробного объяснения: * csharpindepth.com/Articles/General/Beforefieldinit.aspx> * ondotnet.com/pub/a/dotnet/2003/07/07/staticxtor.html>
Дерек, я знаком с «оптимизацией» до поля, но лично меня это не волнует.
рабочая ссылка на комментарий @ DerekPark: csharpindepth.com/Articles/General/Beforefieldinit.aspx. Эта ссылка устарела: ondotnet.com/pub/a/dotnet/2003/07/07/staticxtor.html
Статический конструктор гарантированно потокобезопасен. Также ознакомьтесь с обсуждением Singleton на DeveloperZen: http://web.archive.org/web/20160404231134/http://www.developerzen.com/2007/07/15/whats-wrong-with-this-code-1-discussion/
Хотя все эти ответы дают один и тот же общий ответ, есть одно предостережение.
Помните, что все потенциальные производные от универсального класса компилируются как отдельные типы. Поэтому будьте осторожны при реализации статических конструкторов для универсальных типов.
class MyObject<T>
{
static MyObject()
{
//this code will get executed for each T.
}
}
Обновлено:
Вот демонстрация:
static void Main(string[] args)
{
var obj = new Foo<object>();
var obj2 = new Foo<string>();
}
public class Foo<T>
{
static Foo()
{
System.Diagnostics.Debug.WriteLine(String.Format("Hit {0}", typeof(T).ToString()));
}
}
В консоли:
Hit System.Object
Hit System.String
typeof (MyObject
Я думаю, что это то, что я пытаюсь донести. Универсальные типы компилируются как отдельные типы в зависимости от того, какие общие параметры используются, поэтому статический конструктор может и будет вызываться несколько раз.
Это правильно, когда T имеет тип значения, для ссылочного типа T будет сгенерирован только один универсальный тип
@sll: Неправда ... См. мое редактирование
См. Дженерики в среде выполненияWhen a generic type is first constructed with a value type as a parameter, the runtime creates a specialized generic type with the supplied parameter or parameters substituted in the appropriate places in the MSIL. Specialized generic types are created once for each unique value type used as a parameter
Интересный, но действительно статический конструктор, вызываемый для всех типов, только что пробовал для нескольких ссылочных типов.
@Brian Rudolph: общим является определение статического конструктора, взгляните на этот код
Чтобы быть педантичным, но нет такой вещи, как статический конструктор, а есть инициализаторы статического типа, демонстрация зависимости циклического статического конструктора вот небольшой, которая иллюстрирует этот момент.
Microsoft, похоже, не согласен. msdn.microsoft.com/en-us/library/k9x6w0hc.aspx
Вот версия Cliffnotes с указанной выше страницы MSDN на синглтоне C#:
Всегда используйте следующий шаблон, вы не ошибетесь:
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton(){}
public static Singleton Instance
{
get
{
return instance;
}
}
}
Помимо очевидных функций синглтона, он дает вам эти две вещи бесплатно (относительно синглтона в C++):
Ленивый, если у класса нет другой несвязанной статики (например, const). В противном случае доступ к любому статическому методу или свойству приведет к созданию экземпляра. Так что я бы не назвал это ленивым.
Хотя другие ответы в основном верны, есть еще одно предостережение относительно статических конструкторов.
Согласно разделу II.10.5.3.3 Гонки и тупики Закона ECMA-335 Общий язык Инфраструктура
Type initialization alone shall not create a deadlock unless some code called from a type initializer (directly or indirectly) explicitly invokes blocking operations.
Следующий код приводит к тупиковой ситуации
using System.Threading;
class MyClass
{
static void Main() { /* Won’t run... the static constructor deadlocks */ }
static MyClass()
{
Thread thread = new Thread(arg => { });
thread.Start();
thread.Join();
}
}
Оригинальный автор - Игорь Островский, см. Его пост здесь.
Статический конструктор Конец запустит перед любому потоку, которому разрешен доступ к классу.
private class InitializerTest
{
static private int _x;
static public string Status()
{
return "_x = " + _x;
}
static InitializerTest()
{
System.Diagnostics.Debug.WriteLine("InitializerTest() starting.");
_x = 1;
Thread.Sleep(3000);
_x = 2;
System.Diagnostics.Debug.WriteLine("InitializerTest() finished.");
}
}
private void ClassInitializerInThread()
{
System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() starting.");
string status = InitializerTest.Status();
System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() status = " + status);
}
private void classInitializerButton_Click(object sender, EventArgs e)
{
new Thread(ClassInitializerInThread).Start();
new Thread(ClassInitializerInThread).Start();
new Thread(ClassInitializerInThread).Start();
}
Приведенный выше код дал следующие результаты.
10: ClassInitializerInThread() starting.
11: ClassInitializerInThread() starting.
12: ClassInitializerInThread() starting.
InitializerTest() starting.
InitializerTest() finished.
11: ClassInitializerInThread() status = _x = 2
The thread 0x2650 has exited with code 0 (0x0).
10: ClassInitializerInThread() status = _x = 2
The thread 0x1f50 has exited with code 0 (0x0).
12: ClassInitializerInThread() status = _x = 2
The thread 0x73c has exited with code 0 (0x0).
Несмотря на то, что статический конструктор запускался долго, другие потоки останавливались и ждали. Все потоки читают значение _x, установленное в нижней части статического конструктора.
Это потокобезопасный. Предположим, несколько потоков хотят получить свойство
Instanceсразу. Одному из потоков будет предложено сначала запустить инициализатор типа (также известный как статический конструктор). Между тем, все другие потоки, желающие прочитать свойствоInstance, будут заперто, пока инициализатор типа не завершит работу. Только после завершения инициализатора поля потокам будет разрешено получать значениеInstance. Таким образом, никто не видит, чтоInstanceявляетсяnull.