Потокобезопасный singleton от Ninject

vb_sub

Всем привет, есть необходимость создать потокобезопасный синглтон, чтобы можно было менять в нем данные из разных потоков. К сожалению в документации не нашел 100% ответ будет ли он безопасный в зависимости от разных условий инициализаций. Попытался сам проверить потокобезопасность следующими тестами.
// класс, который должен быть одиночкой

 public class DataStore
 {
 public string Name { get; set; }
 }
Kernel.Bind<DataStore>().ToSelf().InSingletonScope();
//thread-safe Tests

 (new System.Threading.Thread(() =>
 {
 DataLayer.DataStore r1 = ioc.Ioc.Get<DataLayer.DataStore>();
 r1.Name = "From BackgroundThread";
 Debug.WriteLine(r1.Name);
 })).Start();

 DataLayer.DataStore r = ioc.Ioc.Get<DataLayer.DataStore>();
 r.Name = "FromMainthread";
 Debug.WriteLine(r.Name);
//output
//FromMainthread
//FromMainthread
/////////////////////////////////

// те же самые тесты
 Kernel.Bind<Lazy<DataStore>>().ToConstant(new Lazy<DataStore>(
() => new DataStore()
)).InThreadScope();

//From BackgroundThread
//Поток 0x3714 завершился с кодом 0 (0x0).
//FromMainthread

/////////////////////////////////

 Kernel.Bind<DataStore>().ToSelf().InSingletonScope();

//FromMainthread
//FromMainthread
Вопрос- позволяет ли данные второго теста (Kernel.Bind>().ToConstant(new Lazy(() => new DataStore())).InThreadScope();) утверждать, что синглтон потокобезопасен?
24 ответа

vb_sub

vb_sub,Объект безопасный, если все его поля до пятого колена безопасны.Вывод, бери безопасные коллекции.....типа ConcurrenrDictionary.Но у меня иногда и это глючит без блокировки.


vb_sub

Под потокобезопасностью синглтона подразумевается не потокобезопасность его методов или состояния, а потокобезопасное создание экземпляра синглтона (см. у Скита). То, что некий класс является [потокобезопасным] синглтоном, и то, что его свойства/методы потокобезопасны - вещи ортогональные.


vb_sub

Ну и синглетон не имеет отношения к сабжу.Синглетон - экземпляры, а ThreadSafe - доступ.


vb_sub

Сон Веры Павловны,Спс за ссылку.


vb_sub

vb_sub,У меня список для потокового доступа просто находится рядом с главной формой ГУИ. И не надо синглетона).


vb_sub

Под потокобезопасностью синглтона подразумевается не потокобезопасность его методов или состояния, а потокобезопасное создание экземпляра синглтона (см. у Скита). То, что некий класс является [потокобезопасным] синглтоном, и то, что его свойства/методы потокобезопасны - вещи ортогональные.
Я не спец в ninject, но, думаю, что как и всякий приличный DI-контейнер, он должен обеспечить безопасное создание синглетона из коробки без всяких дополнительных телодвижений.


vb_sub

Ninject в глаза не видел, так что могу наврать.InThreadScope по идее указывает на то, что на каждый поток будет создан свой экземпляр "синглтона", т.е. сам класс объекта может быть и не потокобезопасным и у каждого потока будет свой DataStore и своё значение свойства Name.Если делать Lazy прям синглтон, что один для всех, то:Класс Lazy, согласно msdn является потокобезопасным, но вот сам DataStore таким не является.как-то так получается:
var lazy = Kernel.Get<Lazy<DataStore>>(); // норм
var ds = lazy.Value; // норм
ds.Name = ...; // не норм, если операция не будет атомарной, если в set'тер будет добавлена какая-то валидация, например.
В общем, Lazy ничего не даёт и не нужен тут. Ninject и сам по сути роль Lazy выполняет. DataStore конкретно в данном случае синхронизировать не нужно, т.к. на каждый поток будет свой экземпляр. Если прописать InSingletonScope(), тогда и DataStore нужно thread-safe'ить.


vb_sub

Pu4koff,на lazy не стоит заострать внимание-он просто делает отложенную инициализация, на потокобезопасность никак не влияет.Стандартно советуют делать синглтон с lock для поткобезопасности, но он расточителен по ресурсам.Еще метанит приводит такой пример потокобезопасного синглтона без лока
public class Singleton
{
 private static readonly Singleton instance = new Singleton();
 
 public string Name { get; private set; }
 
 private Singleton()
 {
 Name = System.Guid.NewGuid().ToString();
 }
 
 public static Singleton GetInstance()
 {
 return instance;
 }
}
только не очень понятно как Guid сделает его потокобезопасным.


vb_sub

Стандартно советуют делать синглтон с lock для поткобезопасности, но он расточителен по ресурсам.Еще метанит приводит такой пример потокобезопасного синглтона без лока
Я вообще не понимаю - кому в наш век повсеместного DI может понадобиться писать свой синглтон.


vb_sub

vb_sub, под потокобезопасным синглтоном понимают то, что при получении объекта из разных потоков будет получен один и тот же объект. Это не значит что сам объект потокобезопасен, а только то, что можно одновременно вызвать метод:
Singleton.GetInstance()
и получить один и тот же объект.В примере guid используется чисто для проверки. Типа можно запустить кучу потоков, в них выполнить:
Console.WriteLine(Singleton.GetInstance().Name);
и получить одну и ту же строку.Потокобезопасность синглтона там реализуется за счёт статического readonly члена класса со всеми вытекающими плюсами и минусами.При работе с такими "потокобезопасными" синглтонами нужно просто помнить и о потокобезопасности самого объекта, тут обезопасили только метод Singleton.GetInstance() и не более того.


vb_sub

Стандартно советуют делать синглтон с lock для поткобезопасности, но он расточителен по ресурсам.
Даблчек-лок практически не даст оверхеда, разве что кроме первого вызова.


vb_sub

string потокобезопасен поэтому твоя обертка на над ним тоже потокобезопасна.


vb_sub

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


vb_sub

fkthat, я вообще думал, что синглтоны уже несколько лет как перевели в антипаттерны :)
+1


vb_sub

fkthat, я вообще думал, что синглтоны уже несколько лет как перевели в антипаттерны :)
Да херь все это от Башни Слонячей Кости. Точно так же можно сказать, что конструктор это антипаттерн, а использовать надо только фабрики, а еще лучше абстрактные фабрики. К тому же я уже вообще и забыл, как объекты руками создавать - у нас везде когда что-то надо, то добавляешь в конструктор, а IoC сам туда всунет что нужно - синглтон там или не синглтон, или даже вообще какой-то обьект в другом процессе - меня, как его потребителя, это вообще не парит - это забота того, кто контейнер конфигурирует и самого контейнера.


vb_sub

public class DataStore
 {
public ObservableCollection<FakeClass> FakeList { get; set; }
 }
то есть если я предполагаю, что FakeList будет редактироваться из разных потоков, и я не хочу, чтобы какие- либо изменения пропали мне нужно искать Thread-Safe ObservableCollection обертку?


vb_sub

то есть если я предполагаю, что FakeList будет редактироваться из разных потоков, и я не хочу, чтобы какие- либо изменения пропали мне нужно искать Thread-Safe ObservableCollection обертку?
Да. Использовать ReaderWriterLock или ReaderWriterLockSlim.


vb_sub

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


vb_sub

fkthat, это всё из-за модников-неучей. Сначала лепили статические классы. Потом начали задумываться о времени жизни объектов, о реализации интерфейсов и т.д. и т.п. и ломанулись использовать синглтон, типа это такая серебряная пуля, всем и всегда поможет. Статикам табу, синглтонам - милости просим. Потом опять голову включили и оказалось, что у синглтонов тоже не всё круто. Теперь им табу, а на коне всякие DI и сотоварищи.По факту просто всему своё место. И статические классы норм и синглтоны и т.д. и т.п. Законодателям моды проще было сказать, что синглтоны не комильфо пользовать.vb_sub, не забываем еще и про потокобезопасность FakeClass, если возможна работа с одним объектом коллекции из разных потоков.Если разные потоки могут забрать один и тот же элемент коллекции и как-то его менять, тогда его внутренности тоже нужно защищать.И всё равно там не всё так однозначно будет.Типа напишете DataStore.Name = DataStore.Name + DataStore.Nameпо отдельности всё безопасно, а в сумме одна большая беда.


vb_sub

Pu4koff,на lazy не стоит заострать внимание-он просто делает отложенную инициализация, на потокобезопасность никак не влияет.
вообще то в Lazy реализована потоколбезопасность, по этому из 2 потоков получить 2 разных ленивых объекта не получится. https://referencesource.microsoft.com/#mscorlib/system/Lazy.cs,8b99c1f377873554, там даже в конструкторе есть параметр ThreadSafe


vb_sub

Потокобезопасность синглтона там реализуется за счёт статического readonly члена класса со всеми вытекающими плюсами и минусами.
Неправильно. Статическое readonly поле - всего лишь для хранения экземпляра, само по себе оно ничего не гарантирует, потокобезопасность там реализуется либо за счет вручную прописанных локов, либо за счет манпуляций с IL-флагом beforefieldinit, либо за счет обеспечивающих потокобезопасность классов FCL (Lazy).


vb_sub

Неправильно. Статическое readonly поле - всего лишь для хранения экземпляра, само по себе оно ничего не гарантирует, потокобезопасность там реализуется либо за счет вручную прописанных локов, либо за счет манпуляций с IL-флагом beforefieldinit, либо за счет обеспечивающих потокобезопасность классов FCL (Lazy).
Кури спеки. Инициализация статического поля компилятором помещается либо в начало существующего статического конструктора, либо, при отсутствии оного, в сгенерированный. Статический конструктор всегда вызывается в потокобезопасной манере - хоть сколько потоков одновременно обратятся к классу, статический конструктор вызовется только один раз, одним потоком и до выполнения любого кода того же класса. Т.ч. реализовывать синглтон через статическое поле это и есть самый эффективный и простой метод - а всё это дрочево с локами, двойной проверкой и проч. - это только чтобы джуниоров на интервью погномить.


vb_sub

Неправильно. Статическое readonly поле - всего лишь для хранения экземпляра, само по себе оно ничего не гарантирует, потокобезопасность там реализуется либо за счет вручную прописанных локов, либо за счет манпуляций с IL-флагом beforefieldinit, либо за счет обеспечивающих потокобезопасность классов FCL (Lazy).
так в этом и смысл синглтона, что там ровно один экземпляр. Ровно это и даёт статический член класса с инициализацией.такой синглтон не потокобезопасный:
static A GetInstance()
{
 if (_instance == null)
 _instance = new A();
 return _instance;
}
если if обернуть в тот же lock, тогда уже типа будет потокобезопасный синглтон, но это не значит, что стал потокобезопасным возвращенный объект типа А.при таком варианте:
private static readonly Singleton instance = new Singleton();
невозможна же ситуация, что instance в разное время будет ссылаться на разные объекты или что инициализатор new Singleton() отработает несколько раз для разных потоков? А большего от синглтона и его потокобезопасности и не нужно.


vb_sub

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