42. 원자 변수의 복합 연산 비원자성: peak 갱신 유실과 찢긴 평균 쌍 (C#)

난이도 상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// 시나리오: 실시간 전투 통계 집계기. 여러 게임 스레드가 동시에
//  (1) 현재 동시 전투 참가자 수를 보고하며 "관측된 최고치(peak)"를 갱신하고,
//  (2) 히트가 발생할 때마다 총 데미지와 히트 횟수를 누적한다.
// 대시보드는 언제든 평균 데미지(총 데미지 / 히트 횟수)를 조회한다.
//
// 요구사항:
//  1) peak 은 관측된 실제 최댓값이어야 한다(동시 보고에도 유실 없이).
//  2) 평균 = 총데미지 / 히트횟수 는 서로 짝이 맞는(찢기지 않은) 값으로 계산돼야 한다.
//  3) 원자 변수를 쓰더라도 최종 결과가 정확해야 한다.
//
// 아래 집계기를 여러 스레드가 recordConcurrent/recordHit 를 동시에 호출하고
// 대시보드가 AverageDamage 를 동시에 읽는다는 관점에서 검토하고,
// (A)(B) 지점을 중심으로 문제를 진단·수정하라.

using System.Threading;

public class BattleStats
{
    private long _peakConcurrent;
    private long _totalDamage;
    private long _hitCount;

    public void RecordConcurrent(long current)
    {
        // (A) 현재값이 지금까지의 최고치보다 크면 최고치를 갱신한다.
        if (current > Interlocked.Read(ref _peakConcurrent))
            Interlocked.Exchange(ref _peakConcurrent, current);
    }

    public void RecordHit(long damage)
    {
        // (B) 총 데미지와 히트 횟수를 각각 누적한다.
        Interlocked.Add(ref _totalDamage, damage);
        Interlocked.Increment(ref _hitCount);
    }

    public long Peak => Interlocked.Read(ref _peakConcurrent);

    public double AverageDamage()
    {
        long total = Interlocked.Read(ref _totalDamage);
        long count = Interlocked.Read(ref _hitCount);
        return count == 0 ? 0.0 : (double)total / count;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.