41. 계정 공용 창고, 세션 단위 락으로 보호된 공유 자원 (C#)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// [시나리오] MMORPG 계정 공용 창고 (Account Warehouse, 서버-클라)
// 계정 공용 창고는 그 계정에 속한 모든 캐릭터가 공유하는 저장 공간이다.
// 한 계정으로 여러 캐릭터를 만들 수 있고, PC방/멀티클라이언트 환경에서는
// 같은 계정의 서로 다른 캐릭터가 동시에 접속해 있을 수 있다(캐릭터 A로
// 한 클라이언트, 캐릭터 B로 다른 클라이언트).
//
// 창고 슬롯은 슬롯 번호별로 아이템 수량을 보관하며, 임의의 캐릭터가
// 예치(Deposit)/인출(Withdraw)할 수 있다. 캐릭터별 세션은 각자 독립적인
// 네트워크 처리 스레드에서 요청을 처리한다.
//
// 요구사항:
// - 같은 슬롯에서 실제로 인출되는 총량은 그 슬롯의 보유 수량을 넘어설 수 없다
// (슬롯 수량은 음수가 될 수 없다).
// - 같은 계정의 서로 다른 캐릭터가 동시에 인출을 요청해도 위 규칙이 지켜져야
// 한다(세션이 다르다고 해서 별도 자원으로 취급하면 안 된다).
//
// 과제: (A)(B) 지점을 검토하라. 같은 계정의 두 캐릭터가 동시에 같은 슬롯을
// 인출할 때 어떤 조건에서 요구사항이 깨지는지 밝히고 수정하라.
using System.Collections.Generic;
public sealed class CharacterSession
{
public long AccountId;
public long CharacterId;
// 이 캐릭터 세션 전용 락 — 계정 창고 접근을 이 락으로 보호한다고 가정.
public readonly object SessionLock = new object();
}
public sealed class AccountWarehouse
{
public long AccountId;
// slotId -> 수량
public Dictionary<int, int> Slots = new Dictionary<int, int>();
}
public sealed class WarehouseService
{
// accountId -> 창고
private readonly Dictionary<long, AccountWarehouse> _warehouses = new Dictionary<long, AccountWarehouse>();
private AccountWarehouse GetWarehouse(long accountId)
{
lock (_warehouses)
{
if (!_warehouses.TryGetValue(accountId, out var wh))
{
wh = new AccountWarehouse { AccountId = accountId };
_warehouses[accountId] = wh;
}
return wh;
}
}
// 캐릭터가 접속한 세션 스레드에서 호출된다. 같은 계정의 다른 캐릭터가
// 동시에 다른 세션 스레드에서 아래 메서드를 호출할 수 있다.
public bool Withdraw(CharacterSession session, int slotId, int amount)
{
var wh = GetWarehouse(session.AccountId);
// (A) 세션 단위 락 — 같은 계정이라도 캐릭터(세션)가 다르면 서로 다른 락 객체다.
lock (session.SessionLock)
{
if (!wh.Slots.TryGetValue(slotId, out var qty) || qty < amount)
return false;
// (B)
wh.Slots[slotId] = qty - amount;
}
return true;
}
public void Deposit(CharacterSession session, int slotId, int amount)
{
var wh = GetWarehouse(session.AccountId);
lock (session.SessionLock)
{
wh.Slots.TryGetValue(slotId, out var qty);
wh.Slots[slotId] = qty + amount;
}
}
} 결함 코드 · C++
// [시나리오] MMORPG 계정 공용 창고 (Account Warehouse, 서버-클라)
// 계정 공용 창고는 그 계정에 속한 모든 캐릭터가 공유하는 저장 공간이다.
// 한 계정으로 여러 캐릭터를 만들 수 있고, PC방/멀티클라이언트 환경에서는
// 같은 계정의 서로 다른 캐릭터가 동시에 접속해 있을 수 있다(캐릭터 A로
// 한 클라이언트, 캐릭터 B로 다른 클라이언트).
//
// 창고 슬롯은 슬롯 번호별로 아이템 수량을 보관하며, 임의의 캐릭터가
// 예치(deposit)/인출(withdraw)할 수 있다. 캐릭터별 세션은 각자 독립적인
// 네트워크 처리 스레드에서 요청을 처리하고, 접속 종료 시 해당 세션 객체는
// 파괴된다.
//
// 요구사항:
// - 같은 슬롯에서 실제로 인출되는 총량은 그 슬롯의 보유 수량을 넘어설 수 없다
// (슬롯 수량은 음수가 될 수 없다).
// - 같은 계정의 서로 다른 캐릭터가 동시에 인출을 요청해도 위 규칙이 지켜져야
// 한다.
// - 한 캐릭터의 접속 종료가 같은 계정의 다른 캐릭터의 창고 접근에 영향을
// 주어서는 안 된다(다른 세션이 쓰던 자원을 무효화하면 안 된다).
//
// 과제: (A)(B) 지점을 검토하라. 같은 계정의 두 캐릭터가 동시에 같은 슬롯을
// 인출할 때, 그리고 한 캐릭터가 인출 도중 접속을 끊을 때 각각 어떤
// 문제가 생기는지 밝히고 수정하라.
#include <string>
#include <unordered_map>
#include <mutex>
#include <memory>
struct CharacterSession {
long long accountId;
long long characterId;
// 이 캐릭터 세션 전용 락 — 창고 접근을 이 락으로 보호한다고 가정.
std::mutex sessionLock;
};
struct AccountWarehouse {
long long accountId;
// slotId -> 수량
std::unordered_map<int, int> slots;
};
class WarehouseService {
std::mutex mapMx_;
// accountId -> 창고
std::unordered_map<long long, AccountWarehouse> warehouses_;
AccountWarehouse& getWarehouse(long long accountId) {
std::lock_guard<std::mutex> lk(mapMx_);
return warehouses_[accountId]; // 없으면 기본 생성
}
public:
// 캐릭터가 접속한 세션 스레드에서 호출된다. 같은 계정의 다른 캐릭터가
// 동시에 다른 세션 스레드에서 아래 메서드를 호출할 수 있다.
// session 은 호출측이 소유하며, 세션 접속이 끊기면 별도 스레드에서
// 해제(delete)될 수 있다.
bool withdraw(CharacterSession* session, int slotId, int amount) {
AccountWarehouse& wh = getWarehouse(session->accountId);
// (A) 세션 단위 락 — 같은 계정이라도 캐릭터(세션)가 다르면 서로 다른 락 객체다.
std::lock_guard<std::mutex> lk(session->sessionLock);
auto it = wh.slots.find(slotId);
if (it == wh.slots.end() || it->second < amount)
return false;
// (B)
it->second -= amount;
return true;
}
void deposit(CharacterSession* session, int slotId, int amount) {
AccountWarehouse& wh = getWarehouse(session->accountId);
std::lock_guard<std::mutex> lk(session->sessionLock);
wh.slots[slotId] += amount;
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.