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++
// 시나리오: 한 TCP 연결 위에 여러 논리 채널을 다중화(multiplex)한다.
// - High : 이동/피격 등 실시간 상태 갱신 — 작고 지연에 민감.
// - Normal: 채팅/알림 등.
// - Bulk : 월드 스냅샷/인벤토리 전체 동기화 — 수백 KB~수 MB, 지연 비민감.
// 모든 채널의 전송은 "하나의 송신 큐"를 거쳐 송신 스레드가 소켓으로 흘려보낸다.
//
// 요구사항:
// - 시급(High) 패킷은 대량(Bulk) 전송이 진행 중이어도 빠르게 나가야 한다(틱 지연 최소).
// - 한 연결 안의 공정성: 대량 전송이 다른 트래픽을 굶기면 안 된다.
//
// 과제: (A)(B) 를 중심으로, 큰 Bulk 메시지가 큐에 끼었을 때 그 뒤에 들어온
// High 패킷의 전송 지연이 어떻게 되는지 분석하고, 송신 스케줄링의 수정안을 제시하라.
#include <cstdint>
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <atomic>
enum class Channel { High, Normal, Bulk };
struct OutMessage {
Channel channel;
std::vector<std::uint8_t> payload;
};
// 소켓 전송을 흉내 낸 함수: 주어진 바이트를 모두 보낼 때까지 블로킹.
static void write_all(const std::uint8_t* data, std::size_t len);
class SessionSender {
public:
// 어떤 채널이든 같은 큐에 도착 순서대로 넣는다.
void enqueue_send(OutMessage msg) {
{
std::lock_guard<std::mutex> lk(mu_);
queue_.push(std::move(msg)); // (A)
}
cv_.notify_one();
}
void send_loop() {
while (running_.load()) {
OutMessage msg;
{
std::unique_lock<std::mutex> lk(mu_);
cv_.wait(lk, [&] { return !queue_.empty() || !running_.load(); });
if (!running_.load()) return;
msg = std::move(queue_.front());
queue_.pop();
}
// 꺼낸 메시지를 끝까지 다 보낸 뒤에야 다음 메시지로 넘어간다.
write_all(msg.payload.data(), msg.payload.size()); // (B)
}
}
void stop() { running_.store(false); cv_.notify_all(); }
private:
std::mutex mu_;
std::condition_variable cv_;
std::queue<OutMessage> queue_;
std::atomic<bool> running_{true};
};
// (데모용 정의: 실제로는 ::send 루프)
static void write_all(const std::uint8_t*, std::size_t) {}
int main() {
SessionSender s;
s.enqueue_send({Channel::Bulk, std::vector<std::uint8_t>(2 * 1024 * 1024, 0)}); // 2MB
s.enqueue_send({Channel::High, std::vector<std::uint8_t>(32, 1)}); // 작은 시급 패킷
s.stop();
return 0;
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.