22. SemaphoreSlim 의 내부 동작과 async 친화적 대기(WaitAsync)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 주제: SemaphoreSlim 의 내부 동작 — lock/Monitor 와 무엇이 다르고, WaitAsync 는
// "스레드를 블로킹하지 않고 기다린다"는 게 내부적으로 무슨 뜻인가.
//
// 게임서버에서 흔한 패턴: DB/외부 API 호출처럼 동시 호출 수를 제한하고 싶은데,
// 그 호출이 async(비동기 I/O)일 때 lock 으로는 감쌀 수 없다(await 는 lock 블록 안에서
// 쓸 수 없다). SemaphoreSlim 이 그 대안으로 쓰인다.
//
// 다음을 예측·설명하라:
// (1) SemaphoreSlim(initialCount: 2) 로 동시 진입 2개까지 허용할 때, 3번째 호출자는
// Wait()/WaitAsync() 중 무엇을 쓰느냐에 따라 "기다리는 방식"이 어떻게 다른가?
// (스레드풀 스레드를 점유한 채 블로킹 vs 점유하지 않고 대기)
// (2) (A): 왜 lock(obj) { await ...; } 형태는 컴파일이 안 되거나 의도대로 동작하지 않는가?
// SemaphoreSlim.WaitAsync() 는 어떻게 이 문제를 우회하는가?
// (3) (B): Release() 를 호출하는 스레드와 Wait()/WaitAsync() 로 들어간 스레드가 반드시
// 같아야 하는가? lock/Monitor 의 "소유 스레드" 개념과 비교하라(재진입성 포함).
// (4) SemaphoreSlim 카운트가 0일 때 대기자가 여러 명이면, Release() 한 번에 누가 깨어나는지,
// 그 결정이 내부적으로 어떻게 이뤄지는지 설명하라.
using System;
using System.Threading;
using System.Threading.Tasks;
public static class RateLimitedClient
{
// 동시에 최대 2개 요청만 외부 API를 호출하도록 제한
private static readonly SemaphoreSlim s_gate = new SemaphoreSlim(initialCount: 2, maxCount: 2);
public static async Task<string> CallExternalApiAsync(int requestId)
{
// (A) lock(obj) 로는 이 아래 await 를 감쌀 수 없다 — 왜 그런가?
await s_gate.WaitAsync();
try
{
Console.WriteLine($"[{requestId}] entered gate, thread={Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(100); // 외부 API 호출을 흉내
return $"result-{requestId}";
}
finally
{
s_gate.Release(); // (B) Wait 했던 스레드와 Release 하는 스레드가 달라도 되는가?
}
}
public static async Task RunManyAsync(int count)
{
var tasks = new Task<string>[count];
for (int i = 0; i < count; i++)
{
int id = i;
tasks[i] = CallExternalApiAsync(id);
}
await Task.WhenAll(tasks);
}
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.