Microsoft MVP성태의 닷넷 이야기
싱글톤 공부중 질문이 있습니다. [링크 복사], [링크+제목 복사],
조회: 12199
글쓴 사람
농상
홈페이지
첨부 파일
 

public class Singleton
{
    public static Singleton Instance { get; } = new();

    private Singleton()
    {
        Console.WriteLine("생성자 호출");
    }

    static Singleton()
    {
        Console.WriteLine("정적 생성자 호출");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Singleton instance1 = Singleton.Instance;
        Singleton instance2 = Singleton.Instance;

        if (instance1 == instance2)
        {
            Console.WriteLine($"{nameof(instance1)}과 {nameof(instance2)}는 같은 인스턴스입니다.");
        }
    }
}

간단하게 만든 싱글톤 코드입니다.
일반 생성자와 정적 생성자를 정의했고, 프로퍼티로 읽기 접근만 가능하게 했습니다.
여기서 정적 생성자, 일반 생성자 순으로 호출하고 있습니다.
단지 정적 생성자에서 먼저 new로 생성을 한 후 문자열을 출력하기 때문에 일반 생성자 문자열이 먼저 출력되고, 정적 생성자 문자열이 출력됩니다.

여기서 질문
1. 처음으로 프로퍼티에 접근해서 정적 생성자가 호출될 때, new로 인스턴스를 생성하고 반환하는 과정이 Thread-Safe하기 때문에 위 코드가 Thread-Safe한건가요?
2. 처음으로 프로퍼티에 접근할 때 인스턴스를 생성한다면 Lazy Initialization을 구현한 것 아닌가요? Lazy<T>로 구현한 싱글턴과 어떤 차이가 있나요?








[최초 등록일: ]
[최종 수정일: 10/18/2022]


비밀번호

댓글 작성자
 



2022-10-18 10시09분
1. 정적 필드의 초기화 코드는 정적 생성자에 병합됩니다. 그리고 정적 생성자는 thread-safe하므로 제시한 코드가 thread-safe하게 됩니다.

2. 약간 혼동하시는 것 같은데요, singleton은 엄밀히 Lazy<T>와는 무관합니다. 그리고 제시하신 코드가 엄밀히 lazy하다고 볼 수는 없습니다. 가령, C#에서 정의한 모든 타입을 exe가 실행될 때 자동으로 초기에 생성자를 모두 실행하지는 않습니다. 위의 코드는 단순히 정적 생성자에서 new를 하도록 만든 것 뿐이고 인스턴스를 하나만 유지하도록 생성자를 private으로 처리한 것입니다. (그리고 그런 유형을 우리는 singleton이라고 하고.) 만약 저 코드가 lazy하다고 한다면 C#에서 정의한 다른 모든 클래스들도 사용 전까지는 new를 하지 않았을 것이므로 lazy하다고 봐야 합니다.

일례로, 위의 Singleton 클래스에 정적 메서드를 하나 정의한 경우,

    public static void Test()
    {
        Console.WriteLine("TEST");
    }

Singleton.Test() 메서드를 호출하게 되면,

    Singleton.Test(); // 이 단계에서 개체 생성
    Singleton instance1 = Singleton.Instance;

개체 생성이 필요 없음에도 불구하고 정적 생성자에 병합된 생성자 호출이 발생합니다. 여기서 Instance를 Lazy로 바꿔보면 또 어떨까요?

    public static Lazy<Singleton> Instance { get; } = new Lazy<Singleton>(() => new Singleton());

그럼, 이번에는 Singleton.Test를 호출해도 인스턴스 생성자가 호출되지 않습니다.

그렇다면 singleton이 lazy한 것일까요? lazy한 것이 singleton일까요? ^^
정성태
2022-10-18 12시48분
[농상] 아하 Lazy Initialization에 대해 오해하고 있었군요.

정적 필드는 정적 생성자에 병합되고, 정적 생성자는 Thread-Safe하기 때문에 인스턴스를 Thread-Safe하게 생성한다는 말씀이신데,
그렇다면, int[] arr = new int[5] { 1, 2, 3, 4, 5 }나 List<int> list = new(); 같이 일반적으로 new로 생성하는 것은 Thread-Safe한게 아닌가요?
[guest]
2022-10-18 01시36분
왠지 thread-safe에 대해서도 혼동하시는 것 같은데요, 그 코드를 다중 스레드에서 실행했을 때 부작용이 있을까요?
정성태
2022-10-18 03시09분
[농상] 아하 생각해보니 Thread-Safe하지 않네요.
2개 이상의 스레드가 접근하면 어떤 스레드가 먼저 실행하든 new로 인스턴스를 생성할테니 new 자체가 Thread-Safe한지는 별로 중요하진 않군요
[guest]
2022-10-18 03시31분
맞습니다. ^^ new 자체가 thread-safe 하냐는 것은 별 의미가 없습니다. 결국 thread-safe의 기준은 다중 스레드에서 접근했을 때 상태 값이 안전하냐에 따라 나뉘는 것입니다. 따라서 상태를 가지고 있지 않는 경우라면 언제나 thread-safe한 것이고, 상태를 가지고 있다면 다중 스레드에서의 접근에서 안정적인 상태 변경을 만족해야 thread-safe한 것입니다.

억지를 부리자면.... 이럴 수는 있습니다. (마이크로소프트는 그렇게 구현하지 않았지만 만약 어떤 개발자가 임의로 구현한) 사용자 정의 CLR에서 글로벌하게 단일 GC Heap을 구현하고, 그곳에 개체를 생성한 경우 GC Heap의 그다음 위치를 가리키는 포인터 이동을 한다고 가정해 보겠습니다. 만약 다중 스레드에서 new를 해서 해당 포인터 연산의 결과가 비정상적인 상태로 바뀐다면 그때서야 new는 thread-safe하지 않다고 말할 수 있습니다. 아마도 그런 CLR에서라면 C# 개발자는 모든 new 구문에서 locking을 해야만 했을 것입니다.

그런데, 왜 위의 덧글에서 물어본 코드가 thread-safe하지 않다고 말씀하신 거죠? 완전한 코드를 실어주지 않아서 단정지을 수는 없지만 대개의 경우라면 thread-safe일 텐데요.
정성태
2022-10-18 03시57분
[농상] 싱글톤의 사용성을 증가시켜주는 여러 방법을 보던중 Bill Pugh Solution이 Thread-Safe하다고 하기에 new 자체가 Thread-Safe하기 때문에 그런게 아닌가 추측했습니다.
하지만 결과적으로 인스턴스를 생성하는 과정 자체를 넘어서, 등록하는 과정까지를 정적 생성자가 Thread-Safe하게 보장하기 때문에 static만으로도 Thread-Safe를 보장한다는 걸 알았습니다.
[guest]
2022-10-18 04시08분
[농상] 때문에 스레드 1이 인스턴스를 생성하는 동안 스레드 2는 접근하지 못하고 스레드 1이 인스턴스를 등록한 이후에 정적 필드에 접근할 수 있으므로 다음 스레드부터는 인스턴스가 있음을 보장받을 수 있구요
[guest]
2022-10-18 04시26분
아... 위의 덧글에서의 제 질문은, 바로 그 위에 "int[] arr = new int[5] { 1, 2, 3, 4, 5 }나 List<int> list = new(); 같이 일반적으로 new로 생성하는 것은 Thread-Safe한게 아닌가요?"라고 물어보셨고, 그 아래에서 "아하 생각해보니 Thread-Safe하지 않네요."라고 답변을 하셨길래, 왜 그것이 thread-safe하지 않다고 하신 것인지 물어본 것입니다.

참고로, 언급하신 "Bill Pugh Solution"이 아래의 글에 쓰인 내용과 유사할 것 같은데,

Singletons: Bill Pugh Solution or Enum
; https://dzone.com/articles/singleton-bill-pugh-solution-or-enum

사실 그 내용은 Java에 해당하는 것이고, 게다가 C#/C++은 inner static 클래스의 사용법이 자바와는 다르므로 여기에 적용할 수는 없습니다. 닷넷의 경우라면, 그냥 아래의 글 정도로만 알아두시면 될 듯합니다.

C# Singleton 인스턴스 생성
; https://www.sysnet.pe.kr/2/0/896
정성태

... 61  62  [63]  64  65  66  67  68  69  70  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
1053폭풍코딩4/19/201216669스마트클라이언트 ctrl-c 복사가 작동하지 않습니다. [1]
1052에슈리온4/17/201233114ClickOnce 배포시 클라이언트의 설치경로를 지정할 수 없나요? [1]
1051최정수4/10/201220015WCF 클라이언트 비정상 종료 관련 [1]
1050에슈리온4/6/201222166관리자 권한과 ClickOnce, 그리고 Bootstrapper문제 [6]
1049김성혁4/4/201216750스마트클라이언트 stand-alone 방식의 배포 문제.. [1]
1048C#조으다3/27/201220279어셈블리 로드 / 언로드와 관련해서.. [6]
1047김우형3/23/201217893WCF 서비스를 이용한 데이터 전송 중 Exception 문의 [1]
1044임동찬2/29/201217748웹에서 COM Exception.. [2]
1043박성준2/24/201216283Lazy<T> 의 지연객체 생성 전에 실 객체의 Attribute분석 [2]파일 다운로드1
1042이성환2/20/201216709BlockingMethod에 빠진 스레드를 즉시 죽이고 싶습니다...;ㅅ; [2]파일 다운로드1
1039김재영2/15/201216053어셈블리에 사용자마다 다른 값을 적용하여 자동 빌드 방법이 있을까요? [2]
1037윈드로니2/12/201217488WPF 관련 질문 드립니다. [2]
1035임동찬2/9/201215858XML, XSD, XMLCodeGenerator 관련 [2]
1033곽성현2/7/201216384훔..윈도우 디바이스 드라이버 관련 [1]
1030궁빈2/2/201218808msbuild를 이용한 웹게시 및 자동 Dist 방법론에 대해 질문 드립니다!! [6]
1028신정환1/30/201216008기존 legacy Windows application이 WoA 환경에서 동작되지 않는 이유가 무얼까요? [2]
1026노현철1/19/201217968WPF가 XP에서 느리게 로딩되는 현상에 대해 질문드립니다. [4]
1025궁그미1/18/201218935네이버 소켓 로그인 이해가 안되는점이 있는데요 [2]
1022선무당1/11/201217001TFS 에서 공통 Assembly 공유 방법에 대해서 해결책이 없을까요? [2]
1021김재영1/8/201216107대리자를 메소드 파라메터로 넘겨도 됩니까? [2]파일 다운로드1
1019남산골11/13/201918713안녕하세요!! 성태님~~ [3]
1018강한구12/29/201122000WPF ClickOnce 배포시 콤포넌트 dll 다운로드 문제에 대하여 질문 있습니다. [1]
1017임동찬12/28/201118935smart client와 xbop, silverlight [6]
1015개똥이12/22/201116348RMCLOCK 설정 관련 질문이요ㅜ [2]
1011사길수12/16/201116401hyper-v 이더넷 카드 관련 문의 좀 드립니다. [1]
1006루로니12/13/201118500윈도우7과 XP의 DLL후킹 처리에 관해서 질문입니다. [3]
... 61  62  [63]  64  65  66  67  68  69  70  71  72  73  74  75  ...