41. 락프리 스냅샷 교체의 유예 기간 부재: 풀 조기 반납 후 재사용 오염 (C#)

난이도 상 해설 보기 →

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

결함 코드 · C#
// 시나리오: 게임 서버가 자주 읽히고 가끔 통째로 교체되는 "월드 스냅샷"을 공유한다.
// 갱신 스레드(틱)는 새 스냅샷을 만들어 참조를 교체(publish)하고, 읽기 스레드(수십 개
// 세션 워커)는 현재 참조를 읽어 여러 필드를 연달아 사용한다. 할당/GC 압박을 줄이려고
// 스냅샷 객체는 풀(pool)에서 빌려 쓰고, 교체 시 옛 스냅샷은 풀로 돌려준다.
//
// 요구사항:
//  - 읽기 스레드는 lock 없이 저비용으로 현재 스냅샷을 읽는다.
//  - 스냅샷 교체가 읽기 스레드의 진행 중인 참조를 깨뜨리면 안 된다.
//  - 풀로 반납된 스냅샷은 아무도 더는 사용하지 않아야 재사용해도 안전하다.
//
// 과제: Publish 와 ReadTick(그리고 반납 시점)을 검토하고, 읽기 스레드가 무엇을 관측할 수
//       있는지, 어떤 조건에서 드러나는지, 어떻게 고칠지 설명하라. (A)(B)(C) 를 살펴보라.

using System.Threading;

public sealed class WorldSnapshot { public long Tick; public double EventMultiplier; public void Reset() { Tick = 0; EventMultiplier = 0; } }

public sealed class SnapshotPool
{
    public WorldSnapshot Acquire() { /* 풀에서 하나 꺼내 필드 채워 반환 */ return new WorldSnapshot(); }
    public void Release(WorldSnapshot s) { s.Reset(); /* 풀에 되돌려 재사용 대기 */ }  // 이후 Acquire 로 재사용됨
}

public sealed class SnapshotStore
{
    private WorldSnapshot _current;
    private readonly SnapshotPool _pool = new SnapshotPool();

    // 틱 스레드 1개가 호출: 새 스냅샷을 게시하고 옛 것을 풀로 반납한다.
    public void Publish(WorldSnapshot fresh)
    {
        var old = Interlocked.Exchange(ref _current, fresh);   // (A)
        if (old != null) _pool.Release(old);                    // (B)
    }

    // 여러 세션 워커가 동시에 호출: 현재 스냅샷의 필드들을 연달아 사용한다.
    public long ReadTick()
    {
        var s = Volatile.Read(ref _current);
        // ... 여기서 s 의 여러 필드를 연달아 사용하는 처리(생략) ...
        return s?.Tick ?? 0;                                     // (C)
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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