31. 환율 갱신과 환전의 경합 (시세 정합성 / 잔액 lost update)

난이도 중 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 환율이 갱신되는 순간에 환전 요청이 들어오는 상황
// ----------------------------------------------------------------------------
// 시나리오 (거래/경제 - 환전소):
//   - 게임 내 환전소는 골드를 이벤트 코인으로 바꿔 준다.
//   - 시세(환율)는 시장 변동에 따라 백그라운드 갱신 스레드가 주기적으로 바꾼다.
//   - 플레이어는 골드 금액을 입력해 환전을 요청하고, 서버는 "현재 환율"로 코인을 계산해
//     골드를 차감하고 코인을 지급한다.
//
// 상황:
//   - 갱신 스레드가 currentRate_ 를 새 값으로 바꾸는 바로 그 순간에, 여러 플레이어 요청
//     스레드가 Exchange 를 동시에 호출한다.
//   - 같은 플레이어가 빠르게 두 번(또는 클라 재시도로 동시에) 환전을 보낼 수도 있다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾아라. 환율 갱신과 환전이 겹칠 때 지급되는 코인 양과
//   플레이어 골드 잔액이 어떤 상태가 될 수 있는지, 그리고 같은 플레이어의 동시 환전 두 건이
//   어떻게 처리되는지 설명하라. 수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System.Threading;

public interface IWallet
{
    long GetGold(long playerId);
    void SetGold(long playerId, long gold);
    void AddCoin(long playerId, long coin);
}

public class ExchangeMarket
{
    private double currentRate_ = 1.0;     // 골드 1 당 코인 (예: 0.85)

    // 백그라운드 갱신 스레드가 호출
    public void UpdateRate(double newRate)
    {
        currentRate_ = newRate;            // (A) 시세 반영
    }

    public double CurrentRate => currentRate_;   // (B) 시세 조회
}

public class ExchangeService
{
    private readonly IWallet _wallet;
    private readonly ExchangeMarket _market;
    public ExchangeService(IWallet wallet, ExchangeMarket market)
    { _wallet = wallet; _market = market; }

    // 여러 플레이어 요청 스레드에서 동시 호출될 수 있다.
    public bool Exchange(long playerId, long goldAmount)
    {
        if (goldAmount <= 0) return false;

        double rate = _market.CurrentRate;             // (C) 시세 스냅샷
        long coins = (long)(goldAmount * rate);        // (D) 환산

        long bal = _wallet.GetGold(playerId);          // (E) 잔액 조회
        if (bal < goldAmount) return false;            // (F) 잔액 확인

        _wallet.SetGold(playerId, bal - goldAmount);   // (G) 골드 차감
        _wallet.AddCoin(playerId, coins);              // (H) 코인 지급
        return true;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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