15. 매치메이킹: 한 플레이어가 동시에 두 매치에 배정되는 상황 — C#
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 매치메이킹: 한 플레이어가 동시에 두 매치에 배정되는 상황
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어는 여러 게임 모드(예: "랭크 3v3", "일반 3v3")의 매칭 큐에 동시에
// 등록(멀티 큐잉)할 수 있다.
// - 모드마다 전용 매처(matcher) 스레드가 돌면서, 자기 큐에서 후보를 모아
// 인원이 차면 매치를 만든다. 매처들은 서로 다른 스레드에서 병렬로 돈다.
// - 매치가 성사되면 그 플레이어는 다른 모든 큐에서 빠지고, 매치 수락 단계로 간다.
// - 한 플레이어는 동시에 단 하나의 매치에만 배정되어야 한다.
// - 매치 취소/타임아웃 시 플레이어는 다시 Searching 으로 돌아가 재매칭될 수 있다.
//
// 요구사항:
// - 두 매처(다른 모드)가 같은 플레이어를 동시에 잡아 "이중 배정" 되면 안 된다.
// - 매치 성사 시 그 플레이어를 모든 큐에서 일관되게 제거해야 한다(한 큐만 빠지면 안 됨).
// - 플레이어 상태(Searching/InMatch) 판정과 큐 제거는 함께 일어나야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 이중 배정이 일어나는지(인터리빙
// 포함) 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
using System.Linq;
public enum MatchState { Searching, InMatch }
public class WaitingPlayer
{
public long Id;
public int Mmr;
public MatchState State = MatchState.Searching;
// 이 플레이어가 등록한 모드들
public List<int> QueuedModes = new List<int>();
}
public class Match
{
public int Mode;
public List<long> PlayerIds = new List<long>();
}
public class MatchmakingService
{
// mode -> 대기열(등록 순서)
private readonly Dictionary<int, List<WaitingPlayer>> _queues;
private readonly int _teamSize; // 매치 1개에 필요한 인원
public MatchmakingService(Dictionary<int, List<WaitingPlayer>> queues, int teamSize)
{
_queues = queues;
_teamSize = teamSize;
}
// 모드별 매처 스레드가 주기적으로 호출. 매치가 만들어지면 반환, 아니면 null.
public Match TryFormMatch(int mode)
{
List<WaitingPlayer> q = _queues[mode];
// (A) 아직 Searching 인 후보들을 앞에서부터 모은다
var candidates = q.Where(p => p.State == MatchState.Searching)
.Take(_teamSize)
.ToList();
if (candidates.Count < _teamSize)
return null; // 인원 부족
var match = new Match { Mode = mode };
foreach (var p in candidates)
{
// (B) 배정 확정: 상태 전이 + 등록한 모든 큐에서 제거
p.State = MatchState.InMatch;
foreach (int m in p.QueuedModes)
_queues[m].Remove(p);
match.PlayerIds.Add(p.Id);
}
return match;
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 매치메이킹: 한 플레이어가 동시에 두 매치에 배정되는 상황
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어는 여러 게임 모드(예: "랭크 3v3", "일반 3v3")의 매칭 큐에 동시에
// 등록(멀티 큐잉)할 수 있다.
// - 모드마다 전용 매처(matcher) 스레드가 돌면서, 자기 큐에서 후보를 모아
// 인원이 차면 매치를 만든다. 매처들은 서로 다른 스레드에서 병렬로 돈다.
// - 매치가 성사되면 그 플레이어는 다른 모든 큐에서 빠지고, 매치 수락 단계로 간다.
// - 한 플레이어는 동시에 단 하나의 매치에만 배정되어야 한다.
// - 매치 취소/타임아웃 시 플레이어는 다시 Searching 으로 돌아가 재매칭될 수 있다.
//
// 요구사항:
// - 두 매처(다른 모드)가 같은 플레이어를 동시에 잡아 "이중 배정" 되면 안 된다.
// - 매치 성사 시 그 플레이어를 모든 큐에서 일관되게 제거해야 한다(한 큐만 빠지면 안 됨).
// - 플레이어 상태(Searching/InMatch) 판정과 큐 제거는 함께 일어나야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 이중 배정이 일어나는지(인터리빙
// 포함) 설명하고, 수정안과 더 나은 설계를 제시하라. (C++ 고유 위험도 함께 다룰 것)
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <vector>
#include <unordered_map>
#include <memory>
#include <algorithm>
enum class MatchState { Searching, InMatch };
struct WaitingPlayer {
int64_t id = 0;
int mmr = 0;
MatchState state = MatchState::Searching;
std::vector<int> queuedModes; // 이 플레이어가 등록한 모드들
};
struct Match {
int mode = 0;
std::vector<int64_t> playerIds;
};
class MatchmakingService {
public:
MatchmakingService(std::unordered_map<int, std::vector<std::shared_ptr<WaitingPlayer>>>& queues,
int teamSize)
: queues_(queues), teamSize_(teamSize) {}
// 모드별 매처 스레드가 주기적으로 호출. 매치가 만들어지면 반환, 아니면 빈 optional.
std::shared_ptr<Match> TryFormMatch(int mode)
{
std::vector<std::shared_ptr<WaitingPlayer>>& q = queues_[mode];
// (A) 아직 Searching 인 후보들을 앞에서부터 teamSize 만큼 모은다
std::vector<std::shared_ptr<WaitingPlayer>> candidates;
for (auto& p : q) {
if (p->state == MatchState::Searching) {
candidates.push_back(p);
if ((int)candidates.size() == teamSize_) break;
}
}
if ((int)candidates.size() < teamSize_)
return nullptr; // 인원 부족
auto match = std::make_shared<Match>();
match->mode = mode;
for (auto& p : candidates) {
// (B) 배정 확정: 상태 전이 + 등록한 모든 큐에서 제거
p->state = MatchState::InMatch;
for (int m : p->queuedModes) {
auto& mq = queues_[m];
mq.erase(std::remove(mq.begin(), mq.end(), p), mq.end());
}
match->playerIds.push_back(p->id);
}
return match;
}
private:
std::unordered_map<int, std::vector<std::shared_ptr<WaitingPlayer>>>& queues_;
int teamSize_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.