40. 디스패치 스레드 블로킹과 교차 세션 상호 데드락 (C#)
난이도 상내 리뷰 · C#
내 리뷰 · C++
해설 · C#
해설 — 디스패치 스레드 블로킹과 교차 세션 상호 데드락 (C#)
난이도: 상
요약
세션 핸들러(디스패치 스레드에서 실행)가 상대 세션에 보낸 비동기 질의를
.Result 로 동기 블로킹 (A) 한다. 상대의 응답 로직 역시 같은(또는 한정된)
디스패치 스레드에서 실행되므로, (1) 블로킹 동안 그 스레드가 담당하는 다른 모든
세션 처리가 정지하고, (2) 두 세션이 서로에게 질의하면 서로의 응답을 기다리며
영구 교착한다. 최악은 스레드풀 기아까지 겹치는 sync-over-async 데드락이다.
문제점
- [분류: 이벤트 루프/리액터 스레드 블로킹 · sync-over-async 데드락 · 상호 대기]
- 증상: 특정 거래·결투 요청 이후 관련 세션들이 응답 없이 멈추고, 심하면 해당 IO 스레드가 담당하던 무관한 플레이어들까지 렉/무응답. 재현은 간헐적.
- 재현:
- 상호 데드락: A→B 거래 제안 처리 중
AskConfirmTrade(B).Result로 블록. 동시에 B→A 제안이 들어와 B 스레드가AskConfirmTrade(A).Result로 블록. A 는 B 응답을, B 는 A 응답을 기다림 → 둘 다 진행 불가. - 디스패치 기아:
AskConfirmTrade의 continuation 이 실행될 디스패치 스레드를 호출자가.Result로 점유 중이라 continuation 이 스케줄될 스레드가 없어(SynchronizationContext/제한 풀), 자기 자신을 기다림.
- 상호 데드락: A→B 거래 제안 처리 중
- 근본 원인: 동기 대기로 처리 스레드를 점유한 것. 액터 모델의 전제(각 세션은 자기 스레드에서 순차·비블로킹 처리)를 깨고, 처리 흐름 안에서 다른 액터의 완료를 블로킹으로 기다렸다.
수정안
교차 세션 통신을 비동기 메시지 패싱으로 바꾼다. 핸들러를 async 로 만들고
await 로 처리 스레드를 반납한 뒤, 응답이 오면 continuation 에서 이어서 처리한다.
동기 대기(.Result/.Wait())를 제거하는 것이 핵심.
public async Task HandleTradeProposal(SessionActor from, long targetId, TradeOffer offer)
{
var target = _sessions.Get(targetId);
if (target == null) { from.Send(new TradeRejected("상대 없음")); return; }
// await 로 디스패치 스레드를 반납 → 상대 응답 대기 중에도 다른 세션 처리 진행
bool accepted = await target.AskConfirmTrade(from.PlayerId, offer)
.ConfigureAwait(false);
if (accepted) { _sessions.ExecuteTrade(from, target, offer); from.Send(new TradeAccepted()); }
else from.Send(new TradeRejected("상대가 거절"));
}
- 상호 데드락도 사라진다:
await는 스레드를 점유하지 않으므로 A·B 가 서로를 기다려도 각자의 continuation 이 자유롭게 스케줄된다. - 거래처럼 두 자원을 원자적으로 바꿔야 하면, 응답 수신 후 상관관계 ID로 원래
거래 컨텍스트를 찾아
ExecuteTrade를 한 액터(또는 정렬된 락 순서)에서 확정한다.
더 나은 설계
- 교차 엔티티 트랜잭션(거래)은 **제3의 조정자(거래 세션/트랜잭션 액터)**를 두어 두 플레이어에게 각각 비동기로 확인 요청을 보내고, 양쪽 확정을 모아 원자 커밋. 세션 액터끼리 직접 동기 호출하지 않는다.
- 절대 규칙: 디스패치/IO 스레드에서 블로킹 금지(락 대기,
.Result, 동기 DB/RPC). - 트레이드오프: 비동기·상관관계 ID·조정자 도입으로 코드 복잡도가 오른다. 그러나 스레드 점유가 사라져 확장성과 장애 격리가 크게 좋아진다.
면접 포인트
- 왜
.Result/.Wait()가 리액터/제한 스레드 모델에서 데드락을 부르는가 (continuation 이 실행될 스레드를 호출자가 점유). - 액터 간 상호작용은 동기 호출이 아니라 메시지 패싱이어야 하는 이유.
- 두 엔티티 원자 연산을 비동기로 안전하게 확정하는 법(조정자/상관관계 ID/락 순서).
해설 · C++
해설 — 디스패치 스레드 블로킹과 교차 세션 상호 데드락 (C++)
난이도: 상
요약
세션 핸들러(디스패치 스레드에서 실행)가 상대 세션에 넘긴 작업의 future 를
get() 으로 동기 블로킹 (A) 한다. 그 작업은 같은 제한된 디스패치 스레드
풀에서 실행되므로, (1) 블로킹 동안 그 스레드가 담당하는 다른 세션 처리가 정지하고,
(2) 두 세션이 서로 질의하면 서로의 future 를 기다리며 영구 교착한다. 스레드 수가
작으면(예: IO 스레드 N개) 모든 스레드가 서로 대기해 서버 전체가 멈출 수 있다.
문제점
- [분류: 이벤트 루프 스레드 블로킹 · executor 자기 교착 · 상호 대기 데드락]
- 증상: 특정 거래/결투 요청 후 관련 세션 무응답, 심하면 담당 IO 스레드가 처리하던 무관한 세션까지 정지. 스레드 풀이 작을수록 전면 정지에 가까워짐.
- 재현:
- 상호 데드락: A 스레드가
AskConfirmTrade(B).get()로 블록, B 스레드가AskConfirmTrade(A).get()로 블록 → 상호 대기. - executor 자기 교착: 디스패치 스레드가 1개면, 그 스레드가
get()으로 블록된 사이 상대 확인 작업을 실행할 스레드가 없어 future 가 영영 준비되지 않음(자기 자신을 기다림).
- 상호 데드락: A 스레드가
- 근본 원인: 처리 스레드를 동기 대기로 점유해 액터의 비블로킹 전제를 깼다.
수정안
동기 get() 을 제거하고 연속(continuation) 콜백으로 교차 세션 통신을 비동기화한다.
상대에게 확인 요청을 보내고 처리 스레드를 즉시 반납, 응답이 오면 콜백에서 이어 처리.
void HandleTradeProposal(SessionActor* from, int64_t targetId, TradeOffer offer) {
SessionActor* target = sessions_->Get(targetId);
if (!target) { from->Send("TradeRejected:상대 없음"); return; }
// 콜백 기반 비동기 질의: 처리 스레드를 붙잡지 않음
target->AskConfirmTradeAsync(from->playerId, offer,
[this, from, target, offer](bool accepted) {
// 이 콜백은 from 세션 액터의 스레드에 post 되어 실행(순차성 유지)
if (accepted) { sessions_->ExecuteTrade(from, target, offer); from->Send("TradeAccepted"); }
else from->Send("TradeRejected:상대가 거절");
});
// 즉시 반환 → 디스패치 스레드 자유
}
- 상호 데드락 해소: 어느 스레드도 블로킹하지 않으므로 A·B 가 서로 질의해도 각 콜백이 자유롭게 스케줄된다.
- 거래 확정처럼 두 세션 상태를 원자적으로 바꿔야 하면, 콜백에서 정규 락 순서 (playerId 오름차순)로 두 세션을 잠그거나 별도 거래 조정자 액터에서 커밋한다.
더 나은 설계
- 교차 엔티티 트랜잭션은 제3의 거래 조정자가 양쪽에 비동기 확인을 보내고 양쪽 확정을 모아 원자 커밋. 세션 액터 간 직접 동기 호출 금지.
- 규칙: IO/디스패치 스레드에서 블로킹 금지(
future::get, 뮤텍스 장시간 대기, 동기 소켓/DB). 부득이하면 전용 블로킹 풀로 오프로드. - 트레이드오프: 콜백/상관관계로 제어 흐름이 쪼개져 가독성이 떨어진다(코루틴으로 완화 가능). 대신 스레드 점유가 사라져 확장성·장애 격리가 향상.
면접 포인트
- 제한된 executor 에서
future::get()(sync-over-async)가 왜 자기 교착·상호 데드락을 부르는가. - 액터 간 상호작용은 블로킹 호출이 아니라 메시지/콜백이어야 하는 이유.
- 두 세션 원자 연산을 비동기로 안전하게 확정하기(락 순서/조정자/상관관계 ID).