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


79

여기에 설명 된 싱글 톤 패턴에 대한 몇 가지 질문이 있습니다. 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;
      }
   }

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


17
옆으로, Jon Skeet은 Singletons에서 스레드 안전성에 대한 훌륭한 기사를 가지고 있습니다 : csharpindepth.com/Articles/General/Singleton.aspx
Arran

게으른 정적 초기화가 더 좋습니다 ...
Mitch Wheat

1
또한 여기에 설명과 함께 다른 예를 가지고 : csharpindepth.com/Articles/General/Singleton.aspx
세르주 할 Voloshenko

정확히 같은 질문 여기에 자바 세상.
RBT

답변:


133

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

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

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

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


1
유용한 링크와 함께 유익한 답변을 해주셔서 감사합니다. 매우 감사.
Wayne Phipps

이중 확인 잠금-링크가 더 이상 작동하지 않습니다.
El Mac

미안 해요, 다른 사람 말이에요.
El Mac

1
@ElMac : Skeet의 웹 사이트는 ATM이 다운되었습니다. 당연히 백업 될 것입니다. 나는 그것을 명심하고 링크가 올 때 여전히 작동하는지 확인하겠습니다. 감사합니다.

3
.NET 4.0부터이 Lazy<T>작업을 완벽하게 수행합니다.
ilyabreev

34

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) 이상이 필요합니다.


.Net 4.6.1 / C # 6에서이 코드를 사용하면 "System.MissingMemberException : 'lazily-initialized type does not have a public, parameterless constructor.'"메시지가 나타납니다.이 코드는 .Net 4.6.1 / C # 6에서
ttugates

@ttugates, 당신 말이 맞아요, 감사합니다. lazy 객체에 대한 값 팩토리 콜백으로 코드가 업데이트되었습니다.
andasa

14

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

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

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

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

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

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


1
여기 당신의 생각이 정말 마음에 듭니다. 그것을 보는 좋은 방법입니다. 나는이 개 답변 또는 다섯이 하나, 많은 감사 받아 들일 수 있으면 좋겠다
웨인 핍스

2
그것의 시간이 성과를보고 할 때 중요하게 한 결과는, 공유 구조의 차이입니다 수있는 동시에 타격하고 그 것이다 . 때때로 우리는 그러한 행동이 자주 일어날 것이라고 기대하지 않지만, 그렇게 할 수 있기 때문에 잠 가야합니다 (잠금을 한 번만 실패하면 모든 것을 망칠 수 있습니다). 다른 경우에는 많은 스레드가 실제로 동일한 객체에 동시에 충돌한다는 것을 알고 있습니다. 그러나 다른 때는 많은 동시성이있을 것이라고 예상하지 않았지만 틀 렸습니다. 성능을 개선해야하는 경우 동시성이 많은 사용자가 우선합니다.
존 한나

대안에서는 volatile필요하지 않지만이어야합니다 readonly. stackoverflow.com/q/12159698/428724를 참조하십시오 .
wezten

7

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


4

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

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


3

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


인스턴스 변수를 휘발성으로 만들고 있지 않습니까?
Ε Г И І И О

1

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

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

    private Singleton() { }

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

0

이를 이중 확인 잠금 메커니즘이라고합니다. 먼저 인스턴스가 생성되었는지 여부를 확인합니다. 그렇지 않은 경우에만 메서드를 동기화하고 인스턴스를 만듭니다. 응용 프로그램의 성능이 크게 향상됩니다. 잠금을 수행하는 것은 무겁습니다. 따라서 잠금을 방지하려면 먼저 null 값을 확인해야합니다. 이것은 또한 스레드로부터 안전하며 최상의 성능을 달성하는 가장 좋은 방법입니다. 다음 코드를보세요.

public sealed class Singleton
{
    private static readonly object Instancelock = new object();
    private Singleton()
    {
    }
    private static Singleton instance = null;

    public static Singleton GetInstance
    {
        get
        {
            if (instance == null)
            {
                lock (Instancelock)
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}

0

다음 코드 줄이 애플리케이션 시작시 Singleton 인스턴스를 만드는 다른 버전의 Singleton입니다.

private static readonly Singleton singleInstance = new Singleton();

여기서 CLR (공용 언어 런타임)은 개체 초기화 및 스레드 안전성을 처리합니다. 즉, 다중 스레드 환경에서 스레드 안전을 처리하기 위해 명시 적으로 코드를 작성할 필요가 없습니다.

"단일 톤 설계 패턴의 Eager 로딩은 요청 시가 아니라 응용 프로그램 시작시 단일 객체를 초기화하고 나중에 사용할 수 있도록 메모리에 준비해야하는 프로세스가 아닙니다."

public sealed class Singleton
    {
        private static int counter = 0;
        private Singleton()
        {
            counter++;
            Console.WriteLine("Counter Value " + counter.ToString());
        }
        private static readonly Singleton singleInstance = new Singleton(); 

        public static Singleton GetInstance
        {
            get
            {
                return singleInstance;
            }
        }
        public void PrintDetails(string message)
        {
            Console.WriteLine(message);
        }
    }

메인에서 :

static void Main(string[] args)
        {
            Parallel.Invoke(
                () => PrintTeacherDetails(),
                () => PrintStudentdetails()
                );
            Console.ReadLine();
        }
        private static void PrintTeacherDetails()
        {
            Singleton fromTeacher = Singleton.GetInstance;
            fromTeacher.PrintDetails("From Teacher");
        }
        private static void PrintStudentdetails()
        {
            Singleton fromStudent = Singleton.GetInstance;
            fromStudent.PrintDetails("From Student");
        }

멋진 대안하지만 질문에서 언급 한 특정 구현에 잠금 체크에 대해이었다 질문에 대답하지 않습니다
웨인 핍스

직접적으로는 아니지만 대체 "스레드 안전 C # 싱글 톤 패턴"으로 사용할 수 있습니다.
Jaydeep Shil

0

반사 방지 싱글 톤 패턴 :

public sealed class Singleton
{
    public static Singleton Instance => _lazy.Value;
    private static Lazy<Singleton, Func<int>> _lazy { get; }

    static Singleton()
    {
        var i = 0;
        _lazy = new Lazy<Singleton, Func<int>>(() =>
        {
            i++;
            return new Singleton();
        }, () => i);
    }

    private Singleton()
    {
        if (_lazy.Metadata() == 0 || _lazy.IsValueCreated)
            throw new Exception("Singleton creation exception");
    }

    public void Run()
    {
        Console.WriteLine("Singleton called");
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.