49. 완료 카운트다운(Decrement) 종료 판정과 컨텍스트 재사용 경합 (C#)
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 스캐터-개더 집계 컨텍스트의 완료 카운트다운 (C#)
// - 글로벌 검색을 위해 N개 샤드에 하위 요청을 팬아웃한다.
// - 각 하위 요청의 응답 콜백은 결과를 컨텍스트에 담고, 남은 개수(pending)를
// 원자적으로 1 감소시킨다. 0이 되게 한 마지막 콜백이 전체를 완성하고
// 컨텍스트 객체를 풀에 반납한다(할당을 줄이려 GatherContext 를 재사용).
// - 하위 요청/응답은 신뢰성 UDP 기반이라 같은 샤드의 응답이 재전송으로
// 두 번 도착할 수 있다(중복 응답).
//
// 요구사항: 각 개더는 정확히 한 번 완성되고, 컨텍스트는 in-flight 중 반납/재사용
// 되지 않아야 한다.
//
// 과제: 부하 중 간헐적으로 검색 결과가 다른 요청의 결과와 섞이거나, 같은 개더가
// 두 번 완성된다. (A),(B) 를 근거로 원인과 수정안을 설명하라.
using System;
using System.Threading;
using System.Collections.Concurrent;
public sealed class GatherContext
{
public int Pending;
public string?[] Results = new string?[16];
public Action<string?[]>? OnComplete;
}
public sealed class GatherCoordinator
{
private readonly ConcurrentBag<GatherContext> _pool = new();
private GatherContext Rent() => _pool.TryTake(out var c) ? c : new GatherContext();
private void Return(GatherContext c) { c.OnComplete = null; _pool.Add(c); }
public GatherContext Begin(int shardCount, Action<string?[]> onComplete)
{
var ctx = Rent();
ctx.Pending = shardCount;
ctx.OnComplete = onComplete;
return ctx;
}
// 샤드 응답 도착 시 호출 (여러 스레드에서, 중복 도착 가능)
public void OnShardResponse(GatherContext ctx, int shardIndex, string? value)
{
ctx.Results[shardIndex] = value;
if (Interlocked.Decrement(ref ctx.Pending) == 0) // (A)
{
ctx.OnComplete?.Invoke(ctx.Results);
Return(ctx); // (B)
}
}
} 결함 코드 · C++
// 시나리오: 스캐터-개더 집계 컨텍스트의 완료 카운트다운 (C++)
// - 글로벌 검색을 위해 N개 샤드에 하위 요청을 팬아웃한다.
// - 각 응답 콜백은 결과를 컨텍스트에 쓰고 pending 을 원자 감소시킨다.
// 0으로 만든 마지막 콜백이 완성 콜백을 호출하고 컨텍스트를 풀에 반납한다.
// - 신뢰성 UDP 재전송으로 같은 샤드 응답이 두 번 도착할 수 있다(중복 응답).
//
// 요구사항: 각 개더는 정확히 한 번 완성, 컨텍스트는 in-flight 중 반납/재사용 금지.
//
// 과제: 부하 중 결과가 다른 요청과 섞이거나 이중 완성/이중 반납이 발생한다.
// (A),(B) 를 근거로 원인·수정안을 설명하라. (컴파일 가능)
#include <atomic>
#include <vector>
#include <string>
#include <functional>
#include <cstdio>
struct GatherContext {
std::atomic<int> pending{0};
std::vector<std::string> results{16};
std::function<void(const std::vector<std::string>&)> on_complete;
};
class GatherCoordinator {
std::vector<GatherContext*> pool_; // 재사용 풀
public:
GatherContext* Begin(int shard_count,
std::function<void(const std::vector<std::string>&)> cb) {
GatherContext* ctx;
if (!pool_.empty()) { ctx = pool_.back(); pool_.pop_back(); }
else ctx = new GatherContext();
ctx->pending.store(shard_count);
ctx->on_complete = std::move(cb);
return ctx;
}
void Return(GatherContext* ctx) { ctx->on_complete = nullptr; pool_.push_back(ctx); }
// 샤드 응답 도착 (여러 스레드, 중복 가능)
void OnShardResponse(GatherContext* ctx, int shard_index, std::string value) {
ctx->results[shard_index] = std::move(value);
if (ctx->pending.fetch_sub(1) == 1) { // (A) 1->0 전이 감지
ctx->on_complete(ctx->results);
Return(ctx); // (B)
}
}
};
int main() {
GatherCoordinator c;
auto* ctx = c.Begin(2, [](const std::vector<std::string>&){ std::printf("done\n"); });
c.OnShardResponse(ctx, 0, "a");
c.OnShardResponse(ctx, 1, "b");
return 0;
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.