30. Reader-Writer 락 오용: 읽기 락 보유 중 쓰기 승급과 순회 중 변경 (C#)

난이도 중 해설 보기 →

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

결함 코드 · C#
// ─────────────────────────────────────────────────────────────
// 시나리오: 존(zone)의 엔티티 맵은 읽기(검색·주변 조회)가 압도적으로 많고,
//   쓰기(스폰/디스폰)는 가끔이라 ReaderWriterLockSlim으로 보호한다.
//   - Find: 읽기 락으로 단건 조회.
//   - ScanAndCleanup: 주기적으로 전체를 훑어 죽은 엔티티를 제거.
//   - Despawn: 단건 제거(콜백 호출 포함).
//   여러 워커 스레드가 이 메서드들을 동시에 호출한다.
//
// 요구사항:
//   - 읽기는 동시에 여러 개가 진행되어도 되고, 쓰기는 배타적이어야 한다.
//   - 어떤 경로(예외 포함)로 빠져나가도 락이 반드시 풀려야 하고, 스레드들이 서로를 무한히 기다리는 상황이 없어야 한다.
//
// 과제: (A)(B) 지점을 검토하라. ScanAndCleanup이 죽은 엔티티를 만났을 때,
//        그리고 Despawn 도중 예외가 났을 때 무슨 일이 벌어지는지 설명하고
//        수정안을 제시하라.
// ─────────────────────────────────────────────────────────────
using System.Collections.Generic;
using System.Threading;

namespace World
{
    public sealed class Entity { public long Id; public bool IsDead; }

    public sealed class Zone
    {
        private readonly ReaderWriterLockSlim _lock = new();
        private readonly Dictionary<long, Entity> _entities = new();

        public Entity? Find(long id)
        {
            _lock.EnterReadLock();
            try { return _entities.TryGetValue(id, out var v) ? v : null; }
            finally { _lock.ExitReadLock(); }
        }

        public void Spawn(Entity e)
        {
            _lock.EnterWriteLock();
            try { _entities[e.Id] = e; }
            finally { _lock.ExitWriteLock(); }
        }

        public void ScanAndCleanup()
        {
            _lock.EnterReadLock();
            // (A)
            foreach (var kv in _entities)
            {
                if (kv.Value.IsDead)
                {
                    _lock.EnterWriteLock();
                    _entities.Remove(kv.Key);
                    _lock.ExitWriteLock();
                }
            }
            _lock.ExitReadLock();
        }

        public void Despawn(long id)
        {
            _lock.EnterWriteLock();
            // (B)
            var e = _entities[id];     // 키 없으면 예외
            OnDespawn(e);              // 콜백에서 예외가 날 수 있음
            _entities.Remove(id);
            _lock.ExitWriteLock();
        }

        private void OnDespawn(Entity e) { /* 드롭/로그/이벤트 발행(예외 가능) */ }
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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