24. .NET ThreadPool 내부 동작(전역/로컬 큐, work-stealing, 스레드 주입)과 동기 블로킹 기아

난이도 상 해설 보기 →

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

결함 코드 · C#
// 문제 24 — .NET ThreadPool 내부 동작과 동기 블로킹으로 인한 스레드 기아
//
// 아래는 게임 서버에서 매치메이킹 결과를 처리하는 코드다. MatchmakingWorker는
// ThreadPool의 작업 항목으로 큐잉되어 실행되며, 내부에서 다른 비동기 작업의
// 결과를 동기적으로 기다린다(.Result).
//
// (A) FetchPlayerRatingAsync 호출 뒤 .Result로 동기 대기한다.
// (B) ThreadPool.SetMinThreads로 최소 스레드 수를 낮게 고정해 두었다.
//
// 과제:
// 1. ThreadPool에 작업이 큐잉될 때 전역 큐(global queue)와 워커별 로컬 큐
//    (worker-local queue)가 어떻게 나뉘어 쓰이는지, 그리고 "work-stealing"이
//    무엇을 의미하는지 설명하라.
// 2. 아래 코드에서 ProcessMatchesAsync가 N개의 MatchmakingWorker 작업을
//    Task.Run으로 동시에 큐잉했다고 하자. minThreads를 2로 낮춰둔 상태에서
//    각 워커가 내부적으로 FetchPlayerRatingAsync(...).Result 로 블로킹 대기하면
//    무슨 일이 벌어지는지, "스레드 주입(thread injection)" 알고리즘
//    (hill-climbing 휴리스틱)이 왜 이 상황을 바로 해결해주지 못하는지 설명하라.
// 3. ThreadPool이 새 스레드를 추가로 만들어 투입하는 결정을 내리는 주기(대략적인
//    시간 스케일)와, 그 사이에 요청이 왜 느려지거나 타임아웃되는지 설명하라.
// 4. 이 코드를 .Result 대신 await로 바꾸면 스레드 기아 문제가 어떻게 완화되는지
//    설명하라.
//
// 아래 코드는 콘솔에서 컴파일·실행 가능하다(실제 실행 시 동시성/타이밍에 따라
// 출력 순서가 달라질 수 있음에 유의).

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

class MatchmakingWorker
{
    // 실제로는 DB/외부 서비스 호출을 흉내낸 비동기 지연
    static async Task<int> FetchPlayerRatingAsync(int playerId)
    {
        await Task.Delay(200); // I/O 대기를 흉내냄
        return 1000 + playerId;
    }

    // (A) 동기 블로킹: ThreadPool 워커 스레드를 점유한 채로 대기한다.
    static void ProcessOneMatch(int matchId)
    {
        int rating = FetchPlayerRatingAsync(matchId).Result;
        Console.WriteLine($"[Thread {Thread.CurrentThread.ManagedThreadId}] match={matchId} rating={rating}");
    }

    static async Task Main()
    {
        // (B) 최소 스레드 수를 낮게 고정 (기본값보다 훨씬 작게)
        ThreadPool.SetMinThreads(2, 2);

        const int matchCount = 20;
        var sw = Stopwatch.StartNew();

        var tasks = new Task[matchCount];
        for (int i = 0; i < matchCount; i++)
        {
            int matchId = i;
            tasks[i] = Task.Run(() => ProcessOneMatch(matchId));
        }

        await Task.WhenAll(tasks);

        sw.Stop();
        Console.WriteLine($"전체 처리 시간: {sw.ElapsedMilliseconds}ms (minThreads=2, matchCount={matchCount})");
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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