29. 침투적 참조 카운트의 비원자성 (버퍼 풀의 이중 반납·반납 후 사용)
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 풀에서 빌린 네트워크 송수신 버퍼를 여러 스레드(수신 IO 스레드, 처리 워커, 송신 스레드)가
// 동시에 공유한다. 수명 관리를 위해 버퍼에 "침투적 참조 카운트"를 둔다.
// 참조를 늘릴 때 AddRef, 다 쓰면 Release 를 호출하고, 마지막 참조가 사라지면 버퍼를 풀로 반납한다.
//
// 요구사항:
// - 마지막 Release 에서만 "정확히 한 번" 풀로 반납돼야 한다(이중 반납·미반납 금지).
// - 반납된 뒤에는 아무도 그 버퍼를 건드리면 안 된다(반납 후 사용 금지).
//
// 과제: (A)(B) 를 중심으로, 여러 스레드가 동시에 AddRef/Release 할 때
// 참조 카운트가 어떻게 틀어질 수 있는지와 그로 인한 결과, 그리고 수정안을 제시하라.
public sealed class PooledBuffer
{
private int _refs = 1; // 생성 시 1
public byte[] Data { get; } = new byte[2048];
private readonly BufferPool _pool;
public PooledBuffer(BufferPool pool) => _pool = pool;
public void AddRef()
{
_refs++; // (A)
}
public void Release()
{
if (--_refs == 0) // (B)
_pool.Return(this); // 풀로 반납
}
}
public sealed class BufferPool
{
// 반납된 버퍼를 재사용 목록에 넣는다(상세 생략).
public void Return(PooledBuffer buf) { /* ... 재사용 큐에 push ... */ }
public PooledBuffer Rent() => new PooledBuffer(this);
} 결함 코드 · C++
// 시나리오: 풀에서 빌린 네트워크 송수신 버퍼를 여러 스레드(수신 IO 스레드, 처리 워커, 송신 스레드)가
// 동시에 참조한다. 수명 관리를 위해 버퍼에 "침투적 참조 카운트"를 둔다.
// 참조를 늘릴 때 add_ref, 다 쓰면 release 를 호출하고, 마지막 참조가 사라지면 정리(여기선 delete; 실제론 풀 반납)한다.
//
// 요구사항:
// - 마지막 release 시점에만 정확히 한 번 정리돼야 한다.
// - 정리하는 스레드는, 다른 스레드들이 이 버퍼에 마지막으로 쓴 내용까지 모두 관측한 뒤 안전하게 정리해야 한다.
// (다른 스레드의 마지막 사용 ─happens-before─ 이 스레드의 정리, 가 성립해야 한다.)
//
// 과제: (A)(B) 의 메모리 순서(memory order)를 검토하라. 다중 스레드에서 release 가 어떤 순서로
// 겹칠 때 문제가 되는지, 표준 shared_ptr 구현이 쓰는 패턴과 비교해 수정안을 제시하라.
#include <atomic>
class PooledBuffer {
public:
void add_ref() {
// 이미 참조를 쥔 채 늘리는 경우의 증가.
refs_.fetch_add(1, std::memory_order_relaxed);
}
void release() {
if (refs_.fetch_sub(1, std::memory_order_relaxed) == 1) { // (A)
// 마지막 참조: 버퍼를 정리한다.
destroy(); // (B)
}
}
unsigned char* data() { return data_; }
private:
void destroy() { delete this; } // 실제로는 풀로 반납
std::atomic<int> refs_{1}; // 생성 시 1
unsigned char data_[2048];
};
int main() {
auto* b = new PooledBuffer();
b->add_ref();
b->release();
b->release();
return 0;
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.