4. 경매장 동시 입찰

난이도 상 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 경매장 동시 입찰
// ----------------------------------------------------------------------------
// 시나리오:
//   - 플레이어가 아이템을 경매에 등록한다(시작가, 즉시구매가, 마감시각).
//   - 다른 플레이어들이 동시에 입찰한다. 현재 최고가보다 높아야 입찰 성공.
//   - 즉시구매가에 도달하면 즉시 낙찰. 마감되면 최고 입찰자가 낙찰.
//   - 입찰에 성공하면 입찰액만큼 골드가 묶이고(에스크로), 더 높은 입찰자가
//     나타나면 이전 입찰자의 골드는 환불된다.
//
// 요구사항:
//   - 수많은 IO 스레드가 같은 경매에 동시에 입찰 패킷을 처리한다.
//   - 최고가/최고입찰자는 항상 정합적이어야 한다.
//   - 골드는 묶인 만큼 정확히 환불/차감되어야 한다(증발/복사 금지).
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
//   수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Collections.Generic;
using System.Threading;

public class Player
{
    public long Id;
    public long Gold;      // 다른 스레드도 접근
}

public class Auction
{
    private readonly long _itemId;
    private readonly long _buyout;
    private readonly long _closeTick;

    private long _highestBid;          // Interlocked 로 갱신
    private long _highestBidder = -1;  // Interlocked 로 갱신
    private int  _closed;              // 0/1, Interlocked 로 갱신

    public Auction(long itemId, long startPrice, long buyout, long closeTick)
    {
        _itemId = itemId;
        _buyout = buyout;
        _closeTick = closeTick;
        _highestBid = startPrice;
    }

    // 입찰. 성공하면 true. players 에서 골드 묶기/환불 처리.
    public bool Bid(Player bidder, long amount, long nowTick,
                    Dictionary<long, Player> players)
    {
        if (nowTick >= _closeTick) return false;          // (A) 마감 체크

        // (B) 현재 최고가 읽고 비교
        long cur = Interlocked.Read(ref _highestBid);
        if (amount <= cur) return false;

        // (C) 새 최고가로 갱신
        Interlocked.Exchange(ref _highestBid, amount);
        long prevBidder = Interlocked.Exchange(ref _highestBidder, bidder.Id);

        // (D) 새 입찰자 골드 묶기
        Interlocked.Add(ref bidder.Gold, -amount);

        // (E) 이전 입찰자 환불
        if (prevBidder >= 0)
        {
            if (players.TryGetValue(prevBidder, out var prev))
                Interlocked.Add(ref prev.Gold, cur);      // (F)
        }

        // (G) 즉시구매 도달 시 낙찰
        if (amount >= _buyout)
        {
            Interlocked.Exchange(ref _closed, 1);
        }
        return true;
    }

    public bool IsClosed(long nowTick)
    {
        return Interlocked.CompareExchange(ref _closed, 0, 0) == 1 || nowTick >= _closeTick;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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