30. 길드 가입 신청 수락의 동시 처리 (이중 소속 / 정원 초과)
난이도 하 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 길드 가입 신청 수락이 여러 길드에서 동시에 일어나는 상황
// ----------------------------------------------------------------------------
// 시나리오 (커뮤니티/길드):
// - 한 플레이어는 여러 길드에 동시에 가입 "신청"을 넣어 둘 수 있다.
// - 각 길드의 간부가 신청 목록을 보고 "수락"을 누르면 그 플레이어가 길드에 들어온다.
// - 규칙: 한 플레이어는 동시에 한 길드에만 소속될 수 있고, 길드 정원을 넘기면 안 된다.
//
// 상황:
// - 인기 신청자 A 가 길드 G1, G2 두 곳에 신청해 두었다.
// - G1 간부와 G2 간부가 거의 같은 순간에 각자 A 의 신청을 "수락"한다.
// - AcceptApplication 은 서로 다른 스레드(요청)에서 동시에 호출될 수 있다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 두 수락이 동시에 들어왔을 때 A 의 소속과 각 길드의
// 인원/신청 목록이 어떤 상태가 되는지 설명하라. 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System.Collections.Generic;
public class Guild
{
public long Id;
public int Capacity;
public List<long> Members = new();
}
public interface IGuildRepo
{
Guild Get(long guildId);
long? GetPlayerGuild(long playerId); // 소속 길드 id, 없으면 null
void AddMember(long guildId, long playerId);
void RemoveApplication(long playerId, long guildId);
}
public class GuildService
{
private readonly IGuildRepo _repo;
public GuildService(IGuildRepo repo) { _repo = repo; }
// 길드 간부 요청으로 호출. 서로 다른 길드에서 같은 신청자에 대해 동시 호출될 수 있다.
public bool AcceptApplication(long guildId, long playerId)
{
if (_repo.GetPlayerGuild(playerId) != null) // (A) 이미 다른 길드 소속인지
return false;
var g = _repo.Get(guildId);
if (g.Members.Count >= g.Capacity) // (B) 정원 확인
return false;
_repo.AddMember(guildId, playerId); // (C) 소속 반영
g.Members.Add(playerId);
_repo.RemoveApplication(playerId, guildId); // (D) 이 길드 신청만 정리
return true;
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 길드 가입 신청 수락이 여러 길드에서 동시에 일어나는 상황
// ----------------------------------------------------------------------------
// 시나리오 (커뮤니티/길드):
// - 한 플레이어는 여러 길드에 동시에 가입 "신청"을 넣어 둘 수 있다.
// - 각 길드의 간부가 "수락"하면 그 플레이어가 길드에 들어온다.
// - 규칙: 한 플레이어는 동시에 한 길드에만 소속될 수 있고, 길드 정원을 넘기면 안 된다.
//
// 상황:
// - 신청자 A 가 길드 G1, G2 두 곳에 신청해 두었다.
// - G1 간부 스레드와 G2 간부 스레드가 거의 같은 순간에 A 의 신청을 수락한다.
// - 길드마다 별도의 뮤텍스(perGuildMutex)로 그 길드의 멤버 목록을 보호한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 두 수락이 동시에 들어왔을 때 A 의 소속과 각 길드의
// 인원/신청 목록이 어떤 상태가 되는지 설명하라. 길드별 뮤텍스가 보호하는 범위와 공유
// 상태(playerGuild_)의 접근 방식도 함께 분석하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <vector>
#include <unordered_map>
#include <mutex>
#include <optional>
struct Guild {
long id = 0;
int capacity = 0;
std::vector<long> members;
std::mutex mtx; // 이 길드 멤버 목록 보호
};
class GuildService {
public:
// playerGuild_: 플레이어 -> 소속 길드. 없으면 매핑 없음.
std::optional<long> getPlayerGuild(long playerId) {
auto it = playerGuild_.find(playerId);
return it == playerGuild_.end() ? std::nullopt
: std::optional<long>(it->second);
}
// 길드 간부 요청. 서로 다른 길드에서 같은 신청자에 대해 동시 호출될 수 있다.
bool acceptApplication(Guild& g, long playerId) {
if (getPlayerGuild(playerId).has_value()) // (A) 이미 다른 길드 소속인지
return false;
std::lock_guard<std::mutex> lk(g.mtx); // (B) 이 길드 락
if ((int)g.members.size() >= g.capacity) // (C) 정원 확인
return false;
g.members.push_back(playerId); // (D) 멤버 추가
playerGuild_[playerId] = g.id; // (E) 소속 반영
removeApplication(playerId, g.id); // (F) 이 길드 신청만 정리
return true;
}
private:
void removeApplication(long /*playerId*/, long /*guildId*/) { /* ... */ }
std::unordered_map<long, long> playerGuild_; // 전역 소속 맵 (공유)
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.