15. 차단 목록 갱신과 메시지 전달의 경합 (C#)
난이도 하 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 차단 목록 갱신과 귓속말/메시지 전달의 경합
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어는 다른 플레이어를 "차단(block)" 할 수 있다. 차단하면 그 사람이
// 보낸 귓속말/메시지가 나에게 전달되지 않아야 한다.
// - 메시지 전달 워커 스레드가 귓속말을 처리하는 동안, 다른 스레드(소셜 API)는
// 수시로 차단 추가/해제를 처리한다(차단 목록이 동시 변경됨).
// - 차단 판정 규칙: "수신자가 발신자를 차단했는가" 를 검사해야 한다
// (즉, 수신자의 차단 목록에 발신자가 들어 있으면 전달 금지).
//
// 요구사항:
// - 차단 목록이 동시 변경돼도 전달 처리가 예외로 죽지 않아야 한다.
// - 막 차단한 상대의 메시지가 새어 들어오거나, 차단 해제했는데 계속 막히면 안 된다.
// - 차단 판정 방향(누가 누구를 차단했는가)이 정확해야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 깨지는지(동시 인터리빙 포함)
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
public class Player
{
public long Id;
// 내가 차단한 사람들의 id 집합. 소셜 API 스레드가 Add/Remove 한다.
public HashSet<long> Blocked = new HashSet<long>();
public void Deliver(string msg) { /* 세션으로 전송(생략) */ }
}
public class SocialService
{
// playerId -> Player
private readonly Dictionary<long, Player> _players;
public SocialService(Dictionary<long, Player> players) { _players = players; }
// 소셜 API 스레드: 차단 추가/해제
public void Block(long me, long target)
{
var p = _players[me];
p.Blocked.Add(target); // (A)
}
public void Unblock(long me, long target)
{
var p = _players[me];
p.Blocked.Remove(target); // (A)
}
// 메시지 워커 스레드: 귓속말 전달
public void DeliverWhisper(long fromId, long toId, string msg)
{
var to = _players[toId];
// (B) 차단 여부 검사 후 전달
bool blocked = false;
foreach (var id in to.Blocked) // 발신자가 차단 목록에 있는지
{
if (id == fromId) { blocked = true; break; }
}
// (C) 차단이 아니면 전달
if (!blocked)
to.Deliver(msg);
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 차단 목록 갱신과 귓속말/메시지 전달의 경합
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어는 다른 플레이어를 "차단(block)" 할 수 있다. 차단하면 그 사람이
// 보낸 귓속말/메시지가 나에게 전달되지 않아야 한다.
// - 메시지 전달 워커 스레드가 귓속말을 처리하는 동안, 다른 스레드(소셜 API)는
// 수시로 차단 추가/해제를 처리한다(차단 목록이 동시 변경됨).
// - 차단 판정 규칙: "수신자가 발신자를 차단했는가" 를 검사해야 한다.
//
// 요구사항:
// - 차단 목록이 동시 변경돼도 전달 처리가 크래시/UB 로 죽지 않아야 한다.
// - 막 차단한 상대의 메시지가 새거나, 해제했는데 계속 막히면 안 된다.
// - 차단 판정 방향이 정확해야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 깨지는지(동시 인터리빙 포함)
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <string>
#include <unordered_set>
#include <unordered_map>
struct Player {
int64_t id = 0;
// 내가 차단한 사람들의 id 집합. 소셜 API 스레드가 insert/erase 한다.
std::unordered_set<int64_t> blocked;
void Deliver(const std::string& msg) { /* 세션으로 전송(생략) */ }
};
class SocialService {
public:
explicit SocialService(std::unordered_map<int64_t, Player>& players)
: players_(players) {}
// 소셜 API 스레드: 차단 추가/해제
void Block(int64_t me, int64_t target) {
Player& p = players_[me];
p.blocked.insert(target); // (A)
}
void Unblock(int64_t me, int64_t target) {
Player& p = players_[me];
p.blocked.erase(target); // (A)
}
// 메시지 워커 스레드: 귓속말 전달
void DeliverWhisper(int64_t fromId, int64_t toId, const std::string& msg) {
Player& to = players_[toId];
// (B) 차단 여부 검사 후 전달
bool blocked = false;
for (int64_t id : to.blocked) { // 발신자가 차단 목록에 있는지
if (id == fromId) { blocked = true; break; }
}
// (C) 차단이 아니면 전달
if (!blocked)
to.Deliver(msg);
}
private:
std::unordered_map<int64_t, Player>& players_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.