45. Lazy<T> 와 LazyThreadSafetyMode 의 내부 동작

난이도 상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// [주제] Lazy<T> 와 LazyThreadSafetyMode 의 내부 동작
//
// [과제]
// 1. 아래 각 시나리오(A~E)에서 팩토리가 몇 번 호출되는지, 각 스레드가 받는 인스턴스가
//    같은 객체인지, 예외가 발생하면 이후 Value 접근이 어떻게 되는지 예측하시오.
// 2. LazyThreadSafetyMode 의 세 값(None, PublicationOnly, ExecutionAndPublication)이
//    내부적으로 어떤 동기화 전략을 쓰는지 설명하시오. Lazy<T>(bool) 및 인자 없는 생성자가
//    각각 어떤 모드에 해당하는지도 답하시오.
// 3. 시나리오 D 에서 무슨 일이 일어나는지 예측하시오. 단일 스레드로 접근할 때와, 두 스레드가
//    각각 D1.Value 와 D2.Value 를 동시에 시작할 때 결과가 어떻게 달라지는가?
//    모드를 바꾸면 결과가 또 어떻게 달라지는가?
// 4. 시나리오 E 의 예외 캐싱 동작이 모드별로 어떻게 다른지 설명하고, 초기화가 실패할 수
//    있는 자원(DB 커넥션, 원격 설정)에 Lazy<T> 를 쓸 때의 주의점을 설명하시오.
//
// 게임서버 맥락: 전역 설정 테이블, 아이템 스탯 캐시, 공유 커넥션 풀처럼 "처음 필요할 때
// 한 번만 초기화"하는 자원에 널리 쓰인다.

using System;
using System.Threading;
using System.Threading.Tasks;

class Config
{
    public static int FactoryCalls;
    public readonly int Serial;
    public Config() { Serial = Interlocked.Increment(ref FactoryCalls); Thread.Sleep(50); }
}

class Program
{
    // A
    static readonly Lazy<Config> A = new Lazy<Config>(() => new Config());

    // B
    static readonly Lazy<Config> B =
        new Lazy<Config>(() => new Config(), LazyThreadSafetyMode.PublicationOnly);

    // C
    static readonly Lazy<Config> C =
        new Lazy<Config>(() => new Config(), LazyThreadSafetyMode.None);

    // D — 두 Lazy 가 서로의 Value 를 참조한다
    static readonly Lazy<string> D1 = new Lazy<string>(() => "d1:" + D2.Value);
    static readonly Lazy<string> D2 = new Lazy<string>(() => "d2:" + D1.Value);

    // E
    static int _eAttempts;
    static readonly Lazy<Config> E = new Lazy<Config>(() =>
    {
        if (Interlocked.Increment(ref _eAttempts) <= 2) throw new InvalidOperationException("boom");
        return new Config();
    });

    static void Touch(Lazy<Config> lazy, string tag)
    {
        Config.FactoryCalls = 0;
        var results = new Config[8];
        Parallel.For(0, 8, i => results[i] = lazy.Value);

        bool allSame = true;
        for (int i = 1; i < 8; i++) if (!ReferenceEquals(results[0], results[i])) allSame = false;

        Console.WriteLine($"{tag}: factoryCalls={Config.FactoryCalls}, allSame={allSame}");
    }

    static void Main()
    {
        Touch(A, "A");
        Touch(B, "B");
        // C 는 예외로 끝날 수 있다. 왜 그런지도 답안에 포함하라.
        try { Touch(C, "C"); }
        catch (AggregateException ex)
        {
            Console.WriteLine($"C: {ex.InnerException?.GetType().Name}, factoryCalls={Config.FactoryCalls}");
        }

        try { Console.WriteLine("D: " + D1.Value); }
        catch (Exception ex) { Console.WriteLine("D: " + ex.GetType().Name); }

        for (int i = 0; i < 4; i++)
        {
            try { Console.WriteLine($"E{i}: serial={E.Value.Serial}"); }
            catch (Exception ex) { Console.WriteLine($"E{i}: {ex.GetType().Name} attempts={_eAttempts}"); }
        }
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.