Безопасен ли поток статического конструктора C #?

StackOverflow https://stackoverflow.com/questions/7095

  •  08-06-2019
  •  | 
  •  

Вопрос

Другими словами, является ли эта одноэлементная реализация потокобезопасной:

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

    static Singleton()
    {
        instance = new Singleton();
    }

    public static Singleton Instance
    {
        get { return instance; }
    }
}
Это было полезно?

Решение

Статические конструкторы гарантированно запускаются только один раз для каждого домена приложения, перед созданием каких-либо экземпляров класса или обращением к каким-либо статическим членам. http://msdn.microsoft.com/en-us/library/aa645612.aspx

Показанная реализация потокобезопасна для начальной конструкции, то есть для построения одноэлементного объекта не требуется проверка блокировки или нуля.Однако это не означает, что любое использование экземпляра будет синхронизировано.Существует множество способов, которыми это можно сделать;Я показал один из них ниже.

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

Другие советы

Хотя все эти ответы дают один и тот же общий ответ, есть одно предостережение.

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

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

На самом деле использование статического конструктора является потокобезопасный.Статический конструктор гарантированно будет выполнен только один раз.

Из спецификации языка C # http://msdn.microsoft.com/en-us/library/aa645612 (ПРОТИВ 71).aspx:

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

  • Будет создан экземпляр класса.
  • Ссылаются на любой из статических членов класса.

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

Zooba сделал отличное замечание (и за 15 секунд до меня тоже!) о том, что статический конструктор не гарантирует потокобезопасный общий доступ к синглетону.С этим нужно будет справиться другим способом.

Вот версия Cliffnotes с приведенной выше страницы MSDN на c # singleton:

Всегда используйте следующий шаблон, вы не можете ошибиться:

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

   private Singleton(){}

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

Помимо очевидных функций singleton, он предоставляет вам эти две вещи бесплатно (в отношении singleton в c ++).:

  1. отложенное построение (или отсутствие построения, если оно никогда не вызывалось)
  2. синхронизация

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

private static readonly Singleton instance = new Singleton();

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

Тот Самый Спецификация инфраструктуры общего языка гарантирует, что "инициализатор типа должен запускаться ровно один раз для любого заданного типа, если только он явно не вызван пользовательским кодом". (Раздел 9.5.3.1.) Поэтому, если у вас нет какого-то дурацкого IL, вызывающего Singleton::.cctor напрямую (маловероятно), ваш статический конструктор будет запущен ровно один раз перед использованием одноэлементного типа, будет создан только один экземпляр Singleton, и ваше свойство Instance потокобезопасно.

Обратите внимание, что если конструктор Singleton обращается к свойству Instance (даже косвенно), то свойство Instance будет равно null.Лучшее, что вы можете сделать, это определить, когда это происходит, и выдать исключение, проверив, что экземпляр не равен нулю в методе доступа к свойству.После завершения работы вашего статического конструктора свойство Instance будет ненулевым.

Как Ответ Зумбы указывает, что вам нужно будет сделать Singleton безопасным для доступа из нескольких потоков или реализовать механизм блокировки с использованием экземпляра singleton.

Статический конструктор будет финиш Выполняется до того, как любому потоку разрешен доступ к классу.

    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, установленное в нижней части статического конструктора.

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

Статический конструктор гарантированно является потокобезопасным.Кроме того, ознакомьтесь с обсуждением Синглтона на сайте DeveloperZen:http://www.developerzen.com/2007/07/15/whats-wrong-with-this-code-1-discussion/

Хотя другие ответы в основном правильные, есть еще один нюанс со статическими конструкторами.

Согласно разделу II.10.5.3.3 Гонки и тупики из числа Общий язык ECMA-335 Инфраструктура

Инициализация типа сама по себе не должна создавать взаимоблокировку, если только какой-либо код вызываемый из инициализатора типа (прямо или косвенно) явно не вызывает блокирующие операции.

Следующий код приводит к взаимоблокировке

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

Оригинальный автор - Игорь Островский, смотрите его пост здесь.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top