26. 단일 송신 큐의 Head-of-Line 블로킹 (대량 전송이 시급 패킷을 막음)

난이도 중 해설 보기 →

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

결함 코드 · C#
// 시나리오: 한 TCP 연결 위에 여러 논리 채널을 다중화(multiplex)한다.
//   - High  : 이동/피격 등 실시간 상태 갱신 — 작고 지연에 민감.
//   - Normal: 채팅/알림 등.
//   - Bulk  : 월드 스냅샷/인벤토리 전체 동기화 — 수백 KB~수 MB, 지연 비민감.
// 모든 채널의 전송은 "하나의 송신 큐"를 거쳐 송신 루프가 소켓으로 흘려보낸다.
//
// 요구사항:
//   - 시급(High) 패킷은 대량(Bulk) 전송이 진행 중이어도 빠르게 나가야 한다(틱 지연 최소).
//   - 한 연결 안의 공정성: 대량 전송이 다른 트래픽을 굶기면(starve) 안 된다.
//
// 과제: (A)(B) 를 중심으로, 큰 Bulk 메시지가 큐에 끼었을 때 그 뒤에 들어온
//       High 패킷의 전송 지연이 어떻게 되는지 분석하고(실행 시나리오),
//       송신 스케줄링의 수정안을 제시하라.

using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

public enum Channel { High, Normal, Bulk }

public readonly struct OutMessage
{
    public readonly Channel Channel;
    public readonly byte[] Payload;
    public OutMessage(Channel c, byte[] p) { Channel = c; Payload = p; }
}

public sealed class SessionSender
{
    private readonly Stream _stream;
    private readonly ConcurrentQueue<OutMessage> _queue = new();
    private readonly SemaphoreSlim _signal = new(0);

    public SessionSender(Stream stream) => _stream = stream;

    // 어떤 채널이든 같은 큐에 도착 순서대로 넣는다.
    public void EnqueueSend(OutMessage msg)
    {
        _queue.Enqueue(msg);                                            // (A)
        _signal.Release();
    }

    public async Task SendLoopAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            await _signal.WaitAsync(ct);
            if (!_queue.TryDequeue(out var msg)) continue;

            // 꺼낸 메시지를 끝까지 다 보낸 뒤에야 다음 메시지로 넘어간다.
            await _stream.WriteAsync(msg.Payload, 0, msg.Payload.Length, ct);   // (B)
            await _stream.FlushAsync(ct);
        }
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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