C # 정적 생성자 스레드는 안전합니까?


247

다시 말해,이 싱글 톤 구현 스레드는 안전합니다.

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

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

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

1
스레드 안전합니다. 여러 스레드 Instance가 한 번에 속성을 가져오고 싶다고 가정하십시오 . 스레드 중 하나에 먼저 유형 초기화 프로그램 (정적 생성자라고도 함)을 실행하라는 메시지가 표시됩니다. 한편 Instance속성 을 읽으려는 다른 모든 스레드 는 형식 이니셜 라이저가 완료 될 때까지 잠 깁니다 . 필드 이니셜 라이저가 완료된 후에 만 ​​스레드가 Instance값 을 얻을 수 있습니다 . 그래서 아무도 볼 수 없습니다 Instance존재를 null.
Jeppe Stig Nielsen

@JeppeStigNielsen 다른 스레드는 잠기지 않았습니다. 내 자신의 경험에서 나는 그 때문에 심한 오류가 발생했습니다. 첫 번째 스레드 만 정적 이니셜 라이저 또는 생성자를 시작하지만 다른 스레드는 구성 프로세스가 완료되지 않은 경우에도 정적 메소드를 사용하려고합니다.
Narvalex

2
@Narvalex 이 샘플 프로그램 (URL로 인코딩 된 소스)은 설명하는 문제를 재현 할 수 없습니다. 사용중인 CLR 버전에 따라 다를 수 있습니까?
Jeppe Stig Nielsen

@JeppeStigNielsen 시간을 내 주셔서 감사합니다. 당신은 왜 나에게 설명해 주시겠습니까 여기 필드가 오버라이드 (override)됩니다?
Narvalex

5
@Narvalex이 코드로 대문자 X-1 스레딩 없이도 끝납니다 . 스레드 안전성 문제는 아닙니다. 대신 이니셜 라이저 x = -1가 먼저 실행됩니다 (코드의 이전 행, 낮은 행 번호). 그런 다음 이니셜 라이저 X = GetX()가 실행되어 대문자가와 X같습니다 -1. 그런 다음 "명시 적"정적 생성자 인 이니셜 라이저 유형이 static C() { ... }실행되며 소문자 만 변경됩니다 x. 결국, Main메소드 (또는 Other메소드)는 대문자로 읽을 수 있습니다 X. -1스레드가 하나만 있어도 그 값은 입니다.
Jeppe Stig Nielsen

답변:


189

정적 생성자는 클래스의 인스턴스를 만들거나 정적 멤버에 액세스하기 전에 응용 프로그램 도메인 당 한 번만 실행되도록 보장됩니다. https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors

제시된 구현은 초기 구성에 스레드 안전성이 있습니다. 즉, Singleton 오브젝트를 구성하는 데 잠금 또는 널 테스트가 필요하지 않습니다. 그러나 이것이 인스턴스 사용이 동기화된다는 의미는 아닙니다. 이 작업을 수행 할 수있는 다양한 방법이 있습니다. 나는 아래에 하나를 보여 주었다.

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

53
싱글 톤 오브젝트가 변경 불가능한 경우, 뮤텍스 또는 동기화 메커니즘을 사용하는 것은 과도하므로 사용해서는 안됩니다. 또한 위의 샘플 구현은 매우 부서지기 쉽습니다 :-). Singleton.Acquire ()를 사용하는 모든 코드는 Singleton 인스턴스 사용이 끝나면 Singleton.Release ()를 호출해야합니다. 이 작업을 수행하지 못하면 (예 : 조기에 반환, 예외를 통해 범위를 떠나고, 릴리스 호출을 잊어 버림) 다음 번에이 싱글 톤이 다른 스레드에서 액세스 될 때 싱글 톤 .Acquire ()에서 교착 상태가됩니다.
밀라노 Gardian

2
더 나아가겠다고 동의했습니다. 싱글 톤이 불변 인 경우 싱글 톤을 사용하는 것은 과잉입니다. 상수를 정의하십시오. 궁극적으로 싱글 톤을 올바르게 사용하려면 개발자가 자신이하는 일을 알아야합니다. 이 구현만큼 취하기는 어렵지만, 명백히 릴리스되지 않은 뮤텍스가 아닌 이러한 오류가 무작위로 나타나는 문제에서 여전히 낫습니다.
Zooba 2016 년

26
Release () 메서드의 취성을 줄이는 한 가지 방법은 IDisposable이있는 다른 클래스를 동기화 처리기로 사용하는 것입니다. 싱글 톤을 획득하면 핸들러를 가져와 싱글 톤이 필요한 코드를 사용 블록에 넣어 릴리스를 처리 할 수 ​​있습니다.
CodexArcanum

5
이것에 의해 넘어 질 수있는 다른 사람들을 위해 : 초기화가있는 정적 필드 멤버 는 정적 생성자가 호출 되기 전에 초기화됩니다 .
Adam W. McKinley

12
요즘 대답은 Lazy<T>사용하는 것입니다. 원래 게시 한 코드를 사용하는 사람은 잘못하고 있습니다 (솔직히 말해서 5 년 만에 시작하는 것이 좋지 않았습니다. -나는 :) ).
Zooba

86

이 모든 답변이 동일한 일반적인 답변을 제공하지만 한 가지주의 사항이 있습니다.

일반 클래스의 모든 잠재적 파생은 개별 유형으로 컴파일됩니다. 따라서 제네릭 형식에 정적 생성자를 구현할 때는주의하십시오.

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>)! = typeof (MyObject <Y>);
Karim Agha

6
나는 그것이 내가 만들고자하는 요점이라고 생각한다. 제네릭 형식은 사용되는 제네릭 매개 변수에 따라 개별 형식으로 컴파일되므로 정적 생성자를 여러 번 호출 할 수 있습니다.
브라이언 루돌프

1
이것은 T가 값 유형 인 경우에 옳습니다. 참조 유형 T의 경우 하나의 일반 유형 만 생성됩니다
sll

2
@sll : 사실이 아님 ... 내 편집 내용보기
Brian Rudolph

2
흥미롭지 만 정말로 정적 인 cosntructor는 모든 유형을 요구했으며, 여러 참조 유형을 시도했습니다
sll

28

실제로 정적 생성자를 사용하여 입니다 스레드. 정적 생성자는 한 번만 실행됩니다.

C # 언어 사양에서 :

클래스의 정적 생성자는 지정된 응용 프로그램 도메인에서 최대 한 번 실행됩니다. 정적 생성자의 실행은 응용 프로그램 도메인 내에서 발생하는 다음 이벤트 중 첫 번째 이벤트에 의해 트리거됩니다.

  • 클래스의 인스턴스가 작성됩니다.
  • 클래스의 모든 정적 멤버가 참조됩니다.

따라서 싱글 톤이 올바르게 인스턴스화 될 것이라고 믿을 수 있습니다.

Zooba는 정적 생성자가 단일 스레드에 대한 스레드 안전 공유 액세스를 보장하지 않는다는 훌륭한 지적을했습니다. 다른 방법으로 처리해야합니다.


8

C # singleton의 위 MSDN 페이지에있는 Cliffnotes 버전은 다음과 같습니다.

다음 패턴을 사용하십시오. 항상 잘못 될 수는 없습니다.

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

   private Singleton(){}

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

명백한 싱글 톤 기능 외에도 (C ++의 싱글 톤과 관련하여)이 두 가지를 무료로 제공합니다.

  1. 게으른 건설 (또는 호출되지 않은 경우 건설이 아님)
  2. 동기화

3
클래스에 다른 관련없는 정적 요소 (예 : const)가 없으면 지연됩니다. 그렇지 않으면 정적 메서드 나 속성에 액세스하면 인스턴스가 생성됩니다. 그래서 나는 게으르지 않을 것입니다.
Schultz9999

6

정적 생성자는 App Domain 당 한 번만 실행되므로 접근 방식은 정상입니다. 그러나보다 간결한 인라인 버전과 기능상 차이가 없습니다.

private static readonly Singleton instance = new Singleton();

스레드 안전성은 느리게 초기화 할 때 더 중요한 문제입니다.


4
앤드류, 그것은 완전히 동등하지 않습니다. 정적 생성자를 사용하지 않으면 이니셜 라이저 실행시기에 대한 일부 보증이 손실됩니다. 자세한 설명은 다음 링크를 참조하십시오. * < csharpindepth.com/Articles/General/Beforefieldinit.aspx > * < ondotnet.com/pub/a/dotnet/2003/07/07/staticxtor.html >
Derek Park

데릭, 나는 beforefieldinit "최적화"에 익숙 하지만 개인적으로 나는 그것에 대해 걱정하지 않는다.
Andrew Peters

@DerekPark의 의견에 대한 작업 링크 : csharpindepth.com/Articles/General/Beforefieldinit.aspx . 이 링크는 오래되었습니다 : ondotnet.com/pub/a/dotnet/2003/07/07/staticxtor.html
phoog

4

스레드가 클래스에 액세스 하기 전에 정적 생성자가 실행 을 완료 합니다 .

    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 값을 읽습니다.


3

공용 언어 인프라 사양 을 보장하는 "명시 적으로 사용자 코드에 의해 호출하지 않는 한, 특정 유형에 대해 정확히 한 번 실행되어야 초기화 유형." (9.5.3.1 절) 따라서 Singleton ::. cctor를 느슨하게 호출하는 데 약간의 괴상한 IL이 없으면 (아마도) Singleton 유형을 사용하기 전에 정적 생성자가 정확히 한 번만 실행되고 Singleton 인스턴스가 하나만 생성됩니다. 인스턴스 속성은 스레드로부터 안전합니다.

Singleton의 생성자가 Instance 속성에 액세스하더라도 (간접적으로도) Instance 속성은 null입니다. 가장 좋은 방법은 속성 접근 자에서 인스턴스가 null이 아닌지 확인하여이 문제가 발생하는 시점을 감지하고 예외를 throw하는 것입니다. 정적 생성자가 완료되면 Instance 속성은 null이 아닙니다.

으로 Zoomba의 답변 당신이 밖으로 포인트는 여러 스레드에서 액세스 싱글 안전을 만들거나 싱글 인스턴스를 사용하여 주위에 잠금 메커니즘을 구현해야합니다.




0

다른 답변은 대부분 정확하지만 정적 생성자에는 또 다른 경고가 있습니다.

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

원저자는 Igor Ostrovsky 입니다. 여기에서 그의 게시물을 보십시오 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.