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#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.