[c#] 스레드 안전 C # 싱글 톤 패턴

여기에 설명 된 싱글 톤 패턴에 대한 몇 가지 질문이 있습니다.
http://msdn.microsoft.com/en-us/library/ff650316.aspx

다음 코드는 기사에서 발췌 한 것입니다.

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

특히, 위의 예에서 잠금 전후에 인스턴스를 null과 두 번 비교할 필요가 있습니까? 이것이 필요합니까? 먼저 잠금을 수행하고 비교해 보는 것은 어떻습니까?

다음을 단순화하는 데 문제가 있습니까?

   public static Singleton Instance
   {
      get
      {
        lock (syncRoot)
        {
           if (instance == null)
              instance = new Singleton();
        }

         return instance;
      }
   }

잠금을 수행하는 데 비용이 많이 듭니까?



답변

잠금을 수행하는 것은 단순한 포인터 검사와 비교할 때 엄청나게 비쌉니다 instance != null.

여기에 표시되는 패턴을 이중 확인 잠금 이라고 합니다. 그 목적은 한 번만 필요로하는 값 비싼 잠금 작업을 피하는 것입니다 (싱글 톤이 처음 액세스 될 때). 구현은 싱글 톤이 초기화 될 때 스레드 경쟁 조건으로 인한 버그가 없는지 확인해야하기 때문에 그러한 것입니다.

다음과 같이 생각해보십시오. 맨 null체크 (가없는 lock)는 해당 대답이 “예, 객체가 이미 생성되었습니다”일 때만 올바른 사용 가능한 대답을 제공하도록 보장됩니다. 그러나 대답이 “아직 구성되지 않음”이면 충분한 정보가없는 것입니다. “아직 구성되지 않았고 다른 스레드가 곧 구성 할 의도 가 없다는 것”을 알고 싶었 기 때문 입니다. 따라서 외부 검사를 매우 빠른 초기 테스트로 사용하고 답이 “아니오”인 경우에만 적절하고 버그가 없지만 “비싼”절차 (잠금 후 검사)를 시작합니다.

위의 구현은 대부분의 경우에 충분하지만이 시점 에서 다른 대안도 평가하는 C #의 싱글 톤에 대한 Jon Skeet의 기사를 읽어 보는 것이 좋습니다 .


답변

Lazy<T>버전 :

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy
        = new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance
        => lazy.Value;

    private Singleton() { }
}

.NET 4 및 C # 6.0 (VS2015) 이상이 필요합니다.


답변

잠금 수행 : 상당히 저렴합니다 (널 테스트보다 여전히 비쌉니다).

다른 스레드가 잠금을 수행 할 때 잠금 수행 : 잠금을 수행하는 동안 수행해야하는 작업에 대한 비용이 사용자의 시간에 추가됩니다.

다른 스레드가 잠금을 가지고 있고 수십 개의 다른 스레드도 대기 중일 때 잠금 수행 : Crippling.

성능상의 이유로 가능한 한 가장 짧은 기간 동안 다른 스레드가 원하는 잠금을 항상 갖고 싶어합니다.

물론 좁은 것보다 “넓은”자물쇠에 대해 추론하는 것이 더 쉽기 때문에 넓게 시작하고 필요에 따라 최적화하는 것이 가치가 있지만, 좁은 것이 패턴에 맞는 경험과 친숙 함으로부터 배운 경우가 있습니다.

(부수적으로, 그냥 사용할 수 private static volatile Singleton instance = new Singleton()있거나 싱글 톤을 사용할 수 없지만 대신 정적 클래스를 사용할 수 있다면 둘 다 이러한 문제와 관련하여 더 좋습니다).


답변

그 이유는 성능 때문입니다. instance != null(처음을 제외하고는 항상 그럴 것임) 비용이 많이 드는 경우 lock: 초기화 된 싱글 톤에 동시에 액세스하는 두 개의 스레드가 불필요하게 동기화됩니다.


답변

거의 모든 경우 (즉, 첫 번째 경우를 제외한 모든 경우) instance는 null이 아닙니다. 잠금을 획득하는 것은 간단한 확인보다 비용이 많이 들기 때문에 instance잠금 전 값을 한 번 확인 하는 것은 훌륭하고 무료 최적화입니다.

이 패턴을 이중 검사 잠금이라고합니다. http://en.wikipedia.org/wiki/Double-checked_locking


답변

Jeffrey Richter는 다음을 권장합니다.



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

        private Singleton()
        {
        }

        public static Singleton Instance
        {
            get
            {
                if(instance != null) return instance;
                Monitor.Enter(s_lock);
                Singleton temp = new Singleton();
                Interlocked.Exchange(ref instance, temp);
                Monitor.Exit(s_lock);
                return instance;
            }
        }
    }


답변

@andasa의 게으른 버전을 선호하지만 응용 프로그램 요구 사항에 따라 스레드로부터 안전한 Singleton 인스턴스를 열심히 만들 수 있습니다. 이것은 간결한 코드입니다.

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

    private Singleton() { }

    public static Singleton Instance()
    {
        return instance;
    }
}