22. 동점자 타이브레이커와 동시 점수 갱신 (C#)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 동점자 타이브레이커와 동시 점수 갱신
// ----------------------------------------------------------------------------
// 시나리오 (동시성/메모리 · 서버-서버):
// - 실시간 랭킹에 각 플레이어의 "최고 점수(BestScore)" 와 "그 점수에 처음 도달한 시각
// (ReachedAtMs)" 를 기록한다.
// - 랭킹 규칙: 점수가 높을수록 상위. 동점이면 "그 점수에 더 먼저 도달한" 플레이어가 상위
// (타이브레이커 = 더 이른 ReachedAtMs 우선).
// - 점수 갱신은 여러 워커 스레드에서 동시에 들어온다. 새 점수가 기존 최고점보다 높을 때만
// (BestScore, ReachedAtMs) 를 갱신한다. 같은 점수면 ReachedAtMs 를 더 늦게 만들면 안 된다.
//
// 요구사항:
// - (BestScore, ReachedAtMs) 는 항상 짝이 맞아야 한다(찢어진 상태 금지).
// - 동시 갱신에서 더 높은 점수가 유실되면 안 된다(lost update 금지).
// - 타이브레이커가 정확해야 한다(동점 시 더 이른 도달 우선, 늦은 갱신이 시각을 덮지 않음).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 찢어진 상태·갱신 유실·타이브레이커 오류가
// 발생하는지(동시 인터리빙 포함) 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
public class Entry
{
// (A) 점수와 도달시각을 별개 필드로 보관 (둘의 조합은 원자가 아님)
public int BestScore;
public long ReachedAtMs;
}
public class Leaderboard
{
private readonly Dictionary<long, Entry> _table = new(); // 사전 등록됐다고 가정
// 여러 워커 스레드에서 동시 호출
public void Submit(long playerId, int newScore, long nowMs)
{
if (!_table.TryGetValue(playerId, out var e)) return;
int cur = e.BestScore;
// (B) 현재 최고점보다 높으면(또는 같으면) 갱신 — read 후 write 사이에 경쟁
if (newScore >= cur)
{
e.BestScore = newScore;
// (A)(C) 점수와 별개로 시각 갱신 — 동점이어도 시각을 덮어씀
e.ReachedAtMs = nowMs;
}
}
// 랭킹 비교: a 가 b 보다 상위면 true
public static bool Higher(Entry a, Entry b)
{
// (A) 두 필드를 따로 읽어 찢어진 쌍을 볼 수 있음
if (a.BestScore != b.BestScore) return a.BestScore > b.BestScore;
return a.ReachedAtMs < b.ReachedAtMs; // 동점: 더 이른 도달 우선
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 동점자 타이브레이커와 동시 점수 갱신
// ----------------------------------------------------------------------------
// 시나리오 (동시성/메모리 · 서버-서버):
// - 실시간 랭킹에 각 플레이어의 "최고 점수(bestScore)" 와 "그 점수에 처음 도달한 시각
// (reachedAtMs)" 를 기록한다.
// - 랭킹 규칙: 점수가 높을수록 상위. 점수가 같으면(동점) "그 점수에 더 먼저 도달한"
// 플레이어가 상위(타이브레이커 = 더 이른 reachedAtMs 가 우선).
// - 점수 갱신은 여러 워커 스레드에서 동시에 들어온다. 한 플레이어의 새 점수가 기존
// 최고점보다 높을 때만 (bestScore, reachedAtMs) 를 갱신한다. 같은 점수면 reachedAtMs
// 를 더 이르게 만들면 안 된다(이미 도달한 시각이 진짜 최초여야 함).
//
// 요구사항:
// - (bestScore, reachedAtMs) 는 항상 짝이 맞아야 한다(찢어진 상태 금지).
// - 동시 갱신에서 더 높은 점수가 유실되면 안 된다(lost update 금지).
// - 타이브레이커가 정확해야 한다(동점 시 더 이른 도달이 우선, 늦은 갱신이 시각을 덮지 않음).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 찢어진 상태·갱신 유실·타이브레이커 오류가
// 발생하는지(동시 인터리빙 포함) 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <atomic>
#include <unordered_map>
struct Entry {
// (A) 점수와 도달시각을 별개 atomic 으로 보관 (각각은 원자, 둘의 조합은?)
std::atomic<int32_t> bestScore{0};
std::atomic<int64_t> reachedAtMs{0};
};
class Leaderboard {
public:
// 여러 워커 스레드에서 동시 호출: 플레이어의 새 점수 보고
void submit(int64_t playerId, int32_t newScore, int64_t nowMs) {
Entry& e = table_[playerId]; // (사전 등록됐다고 가정)
int32_t cur = e.bestScore.load(std::memory_order_relaxed);
// (B) 현재 최고점보다 높으면(또는 같으면) 갱신
if (newScore >= cur) {
// (B) read 후 write — 그 사이 다른 스레드가 더 큰 값을 썼을 수 있음
e.bestScore.store(newScore, std::memory_order_relaxed);
// (A)(C) 점수 store 와 별개로 시각 store — 동점이어도 시각을 덮어씀
e.reachedAtMs.store(nowMs, std::memory_order_relaxed);
}
}
// 랭킹 비교: a 가 b 보다 상위면 true
static bool higher(const Entry& a, const Entry& b) {
int32_t sa = a.bestScore.load(std::memory_order_relaxed);
int32_t sb = b.bestScore.load(std::memory_order_relaxed);
if (sa != sb) return sa > sb;
// 동점: 더 이른 도달이 상위
return a.reachedAtMs.load(std::memory_order_relaxed)
< b.reachedAtMs.load(std::memory_order_relaxed);
}
private:
std::unordered_map<int64_t, Entry> table_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.