50. 경매장 즉시구매와 가격 인하의 동시 처리 (이중 판매 / 가격 불일치)
난이도 하 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] 경매장 즉시구매(buyout)와 판매자 가격 인하가 동시에 일어나는 상황
// ----------------------------------------------------------------------------
// 시나리오:
// MMORPG 경매장에서 각 매물(Listing)은 즉시구매가(BuyoutPrice)를 가진다.
// - 구매자는 "즉시구매" 버튼을 눌러 표시된 가격에 매물을 산다.
// - 판매자는 매물이 팔리기 전 언제든 즉시구매가를 내려(또는 올려) 수정할 수 있다.
// 여러 지역/스레드에서 오는 요청을 서버가 동시에 처리한다.
//
// 요구사항:
// - 하나의 매물은 정확히 한 번만 판매되어야 한다(이중 판매/이중 지급 금지).
// - 구매자가 실제로 지불하는 금액과 아이템 지급, 판매자 정산은 서로 어긋나지 않아야 한다.
// - 여러 요청이 동시에 같은 매물을 건드려도 상태가 깨지지 않아야 한다.
//
// 과제:
// 아래 AuctionHouse 의 BuyoutAsync 와 EditBuyoutPrice 를 검토하라.
// (A)(B) 위치를 중심으로 동시 요청이 겹칠 때 어떤 결과가 나올 수 있는지,
// 그리고 어떻게 고쳐야 하는지 설명하라.
// ============================================================================
using System.Collections.Generic;
public sealed class Listing
{
public long Id;
public long SellerId;
public int ItemId;
public long BuyoutPrice;
public bool Sold;
}
public sealed class AuctionHouse
{
private readonly Dictionary<long, Listing> _listings = new();
private readonly IWallet _wallet;
private readonly IInventory _inv;
public AuctionHouse(IWallet wallet, IInventory inv) { _wallet = wallet; _inv = inv; }
public BuyResult Buyout(long buyerId, long listingId)
{
if (!_listings.TryGetValue(listingId, out var listing)) // (A)
return BuyResult.NotFound;
if (listing.Sold)
return BuyResult.AlreadySold;
long price = listing.BuyoutPrice;
if (_wallet.GetGold(buyerId) < price)
return BuyResult.NotEnoughGold;
_wallet.Deduct(buyerId, price);
listing.Sold = true; // (B)
_inv.Give(buyerId, listing.ItemId, 1);
_wallet.Add(listing.SellerId, price);
return BuyResult.Ok;
}
public void EditBuyoutPrice(long sellerId, long listingId, long newPrice)
{
if (_listings.TryGetValue(listingId, out var listing) && listing.SellerId == sellerId)
listing.BuyoutPrice = newPrice;
}
}
public enum BuyResult { Ok, NotFound, AlreadySold, NotEnoughGold }
public interface IWallet { long GetGold(long id); void Deduct(long id, long amt); void Add(long id, long amt); }
public interface IInventory { void Give(long id, int itemId, int count); } 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] 경매장 즉시구매(buyout)와 판매자 가격 인하/취소가 겹치는 상황
// ----------------------------------------------------------------------------
// 시나리오:
// MMORPG 경매장에서 각 매물(Listing)은 즉시구매가(buyoutPrice)를 가진다.
// - 구매자 요청 스레드가 "즉시구매" 를 처리한다.
// - 판매자 요청 스레드는 매물이 팔리기 전 가격을 수정하거나 매물을 취소(삭제)할 수 있다.
//
// 요구사항:
// - 매물은 정확히 한 번만 판매(이중 판매/이중 지급 금지).
// - 구매자 지불액·아이템 지급·판매자 정산이 어긋나지 않아야 한다.
// - 취소/수정과 구매가 동시에 일어나도 안전해야 한다.
//
// 과제:
// AuctionHouse 의 buyout / editPrice / cancel 을 검토하라.
// (A)(B) 위치를 중심으로 동시 요청이 겹칠 때의 결과와 수정 방향을 설명하라.
// ============================================================================
#include <unordered_map>
#include <cstdint>
struct Listing {
int64_t id;
int64_t sellerId;
int itemId;
int64_t buyoutPrice;
bool sold = false;
};
struct IWallet { virtual int64_t gold(int64_t) = 0; virtual void deduct(int64_t,int64_t)=0; virtual void add(int64_t,int64_t)=0; virtual ~IWallet()=default; };
struct IInventory { virtual void give(int64_t,int,int)=0; virtual ~IInventory()=default; };
enum class BuyResult { Ok, NotFound, AlreadySold, NotEnoughGold };
class AuctionHouse {
public:
AuctionHouse(IWallet& w, IInventory& inv) : wallet_(w), inv_(inv) {}
BuyResult buyout(int64_t buyerId, int64_t listingId) {
auto it = listings_.find(listingId);
if (it == listings_.end()) return BuyResult::NotFound;
Listing* l = &it->second; // (A) 매물 포인터 확보
if (l->sold) return BuyResult::AlreadySold;
int64_t price = l->buyoutPrice;
if (wallet_.gold(buyerId) < price) return BuyResult::NotEnoughGold;
wallet_.deduct(buyerId, price);
l->sold = true; // (B)
inv_.give(buyerId, l->itemId, 1);
wallet_.add(l->sellerId, price);
return BuyResult::Ok;
}
void editPrice(int64_t sellerId, int64_t listingId, int64_t newPrice) {
auto it = listings_.find(listingId);
if (it != listings_.end() && it->second.sellerId == sellerId)
it->second.buyoutPrice = newPrice;
}
void cancel(int64_t sellerId, int64_t listingId) {
auto it = listings_.find(listingId);
if (it != listings_.end() && it->second.sellerId == sellerId)
listings_.erase(it); // 매물 제거
}
void put(const Listing& l) { listings_[l.id] = l; }
private:
std::unordered_map<int64_t, Listing> listings_;
IWallet& wallet_;
IInventory& inv_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.