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

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
정성태

1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
5730김재환10/7/20224518WPF에서 디스플레이 배율이 100%가 아닌 경우, Window의 포지션 정보가 부정확해지는 문제 [2]
5729김기헌10/2/20224180안녕하세요 선생님 뮤텍스 관련 질문 드립니다 [2]
5728김경환9/29/20224200그리드뷰관련마지막질문하나드리겠습니다선생님 [5]파일 다운로드1
5727김경환9/26/20224073c# 윈폼 tcp/ip 기반 데이터그리드뷰질문하나드리겟습니다 [3]
5726양승조 donator9/22/20224789C# dll 과 C++ 간 배열 전달. SafeArray [10]파일 다운로드1
5725김기헌9/21/20223686안녕하세요 선생님 윈폼 컨트롤 Dispose 관련 질문드립니다 [2]
5724감사합니...9/19/20223725스레드와 스레드 안전한 객체 사용관련 문의드립니다. [5]
5723드리렁9/13/20223645Pinned Object에 대해서 질문이 있습니다. [2]
5722김인태9/8/20223826대화상자에서 alt + tab 후킹 작업 [1]
5721우종9/7/20223977C++ DLL 과 C# 연동 문의 [2]
5720한예지 donator9/6/20223687학습 방법 질문 있습니다. [7]
5719김경한9/6/20224025안녕하세요 질문하나만드리겠습니다...! [10]
5718김민아9/2/20224049안녕하세요 생성자 호출 시 초기화 순서 질문드립니다 [2]
5716iili...8/26/20224285WinDbg 커널 디버깅에서의 thread freeze [2]
5715에릭8/19/20224687WMI 쿼리 결과값이 Windows Service와 Console 출력에서 상이한 이유가 있을까요? [9]파일 다운로드1
5714허니빠8/18/20224714.net6 hint path 를 프로젝트 단위로 지정할 수 있는 방법을 알고싶습니다 [8]
5713김기헌8/17/20224637안녕하세요 rgb 계산 오차가 있는데 원인을 모르겠습니다.. [3]
5712하태8/17/20224125안녕하세요 background service에서 user32dll 접근 질문 드리겠습니다.! [2]
5711하태8/16/20223644안녕하세요! 윈도우즈 해상도 관련 질문 드립니다. [1]
5710장성욱8/12/20223859c# 시리얼 통신 관련 질문 [3]
5709초보8/12/20223653WPF 커맨드 관련 질문 [2]
5708민성8/11/20223914안녕하세요 c#에서 화면의 배율 및 레이아웃을 변경할려면 어떻게 해야 할까요? [2]파일 다운로드1
5707민성8/10/20223541WPF 엣지 컨트롤에서 화면이 안보이는 현상 [2]파일 다운로드1
5706종규8/7/20225963WPF 에서 SVG 아이콘 사용 방법 문의 [2]
5705김기헌8/6/20224682안녕하세요 선생님 싱글톤 패턴 간단 질문 [2]
5704따봉이8/4/20224562EventHandler 관련 [1]
1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...