21. 일일/주간 초기화 시점과 진행도 갱신이 겹치는 상황 (C#)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 일일/주간 초기화 시점과 진행도 갱신이 겹치는 상황
// ----------------------------------------------------------------------------
// 시나리오 (영속화/동시성 · 콘텐츠):
// - 플레이어는 "일일 미션" 진행도(처치 수 등)를 가진다. 매일 자정(서버 기준)에
// 스케줄러가 모든 플레이어의 일일 진행도를 0 으로 초기화(reset)한다.
// - 동시에 게임 로직 스레드들은 플레이어 활동마다 진행도를 증가시킨다(AddProgress).
// 자정 근처에도 플레이어는 계속 플레이 중일 수 있다.
// - 초기화는 단일 스케줄러 스레드가 전체 플레이어를 순회하며 수행하고, AddProgress 는
// 여러 게임 워커 스레드에서 동시에 호출된다.
//
// 요구사항:
// - 초기화 경계 직전/직후의 진행도 증가가 "유실" 되거나 "엉뚱한 날짜에 귀속" 되면 안 된다.
// - 일일 진행도는 자정에 정확히 한 번만 0 으로 리셋돼야 한다(이중 리셋/리셋 누락 금지).
// - 초기화 순회 중 자료구조가 손상되거나 예외가 나면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 진행도 유실·오귀속·손상이 발생하는지
// (동시 인터리빙 포함) 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System.Collections.Generic;
public class DailyMission
{
public int Day = 0; // 이 진행도가 속한 "날짜"(예: epoch day)
public int Progress = 0; // 오늘 누적 진행도
public int Target = 10; // 완료 목표
public bool ClaimedToday = false;
}
public class DailyMissionManager
{
private readonly Dictionary<long, DailyMission> _players = new();
// 게임 워커 스레드들이 동시에 호출
public void AddProgress(long playerId, int amount, int currentDay)
{
// (A) 없으면 기본 생성(인덱서 set)
if (!_players.TryGetValue(playerId, out var dm))
{ dm = new DailyMission(); _players[playerId] = dm; }
// (B) 날짜가 바뀌었으면 먼저 리셋하고 누적
if (dm.Day != currentDay)
{
dm.Day = currentDay;
dm.Progress = 0;
dm.ClaimedToday = false;
}
dm.Progress += amount; // (C) 진행도 누적
}
// 스케줄러 스레드가 자정에 1회 호출: 전체 플레이어 일일 진행도 초기화
public void ResetAll(int newDay)
{
foreach (var kv in _players) // (D) 전체 순회하며 리셋
{
var dm = kv.Value;
dm.Day = newDay;
dm.Progress = 0;
dm.ClaimedToday = false;
}
}
public bool IsComplete(long playerId)
=> _players.TryGetValue(playerId, out var dm) && dm.Progress >= dm.Target;
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 일일/주간 초기화 시점과 진행도 갱신이 겹치는 상황
// ----------------------------------------------------------------------------
// 시나리오 (영속화/동시성 · 콘텐츠):
// - 플레이어는 "일일 미션" 진행도(예: 처치 수, 채집 수)를 가진다. 매일 자정(서버 기준)에
// 스케줄러가 모든 플레이어의 일일 진행도를 0 으로 초기화(reset)한다.
// - 동시에, 게임 로직 스레드들은 플레이어가 활동할 때마다 진행도를 증가시킨다
// (AddProgress). 자정 근처에도 플레이어는 계속 플레이 중일 수 있다.
// - 초기화 작업은 단일 스케줄러 스레드가 전체 플레이어를 순회하며 수행하고,
// AddProgress 는 여러 게임 워커 스레드에서 동시에 호출된다.
//
// 요구사항:
// - 초기화 경계 직전/직후의 진행도 증가가 "유실" 되거나 "엉뚱한 날짜에 귀속" 되면 안 된다.
// - 일일 진행도는 자정에 정확히 한 번만 0 으로 리셋돼야 한다(이중 리셋/리셋 누락 금지).
// - 초기화 순회 중 자료구조가 손상되거나 UB 가 나면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 진행도 유실·오귀속·손상이 발생하는지
// (동시 인터리빙 포함) 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <unordered_map>
struct DailyMission {
int day = 0; // 이 진행도가 속한 "날짜"(예: epoch day)
int progress = 0; // 오늘 누적 진행도
int target = 10; // 완료 목표
bool claimedToday = false;
};
class DailyMissionManager {
public:
// 게임 워커 스레드들이 동시에 호출
void AddProgress(std::int64_t playerId, int amount, int currentDay) {
DailyMission& dm = players_[playerId]; // (A) 없으면 기본 생성
// (B) 날짜가 바뀌었으면 먼저 리셋하고 누적
if (dm.day != currentDay) {
dm.day = currentDay;
dm.progress = 0;
dm.claimedToday = false;
}
dm.progress += amount; // (C) 진행도 누적
}
// 스케줄러 스레드가 자정에 1회 호출: 전체 플레이어 일일 진행도 초기화
void ResetAll(int newDay) {
for (auto& kv : players_) { // (D) 전체 순회하며 리셋
DailyMission& dm = kv.second;
dm.day = newDay;
dm.progress = 0;
dm.claimedToday = false;
}
}
bool IsComplete(std::int64_t playerId) {
auto it = players_.find(playerId);
return it != players_.end() && it->second.progress >= it->second.target;
}
private:
std::unordered_map<std::int64_t, DailyMission> players_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.