25. 작업 큐 워커의 조건 대기 술어 재확인 부재 (spurious/다중 소비 wakeup)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 작업 큐 워커의 조건 대기(Monitor.Wait) 술어 재확인 부재
// ----------------------------------------------------------------------------
// 시나리오 (동시성/메모리 · 서버 내부):
// - 서버는 저장/브로드캐스트 같은 일을 백그라운드 작업 큐로 넘긴다.
// - 워커 스레드(들)가 큐에서 작업을 꺼내 처리하고, 큐가 비면 대기한다.
// - 생산자(여러 IO/로직 스레드)가 Enqueue 로 작업을 넣고 워커를 깨운다.
// - 워커는 한 개일 수도, 여러 개일 수도 있다.
//
// 요구사항:
// - 워커는 큐에 작업이 있을 때만 꺼내야 한다(빈 큐에서 꺼내면 안 된다).
// - 깨어났다고 해서 항상 처리할 작업이 있다고 가정하면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 워커가 여러 개이거나 대기에서 깨어났을 때 (B)~(D)
// 구간에서 _q 에 무슨 일이 일어날 수 있는지 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
using System.Threading;
public class Job { public Action Run = () => { }; }
public class JobQueue
{
private readonly Queue<Job> _q = new();
private readonly object _lock = new();
// 생산자: 작업을 넣고 워커를 깨운다
public void Enqueue(Job job)
{
lock (_lock)
{
_q.Enqueue(job);
Monitor.Pulse(_lock); // (A) 대기 중인 워커 하나를 깨움
}
}
// 워커 스레드 루프
public void WorkerLoop()
{
while (true)
{
Job job;
lock (_lock)
{
if (_q.Count == 0) // (B) 비었으면 대기
Monitor.Wait(_lock); // (C) 깨어나면 진행
job = _q.Dequeue(); // (D) 꺼내서 처리
}
job.Run();
}
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 작업 큐 워커의 condition_variable 대기(술어 없는 wait)
// ----------------------------------------------------------------------------
// 시나리오 (동시성/메모리 · 서버 내부):
// - 서버는 저장/브로드캐스트 같은 일을 백그라운드 작업 큐로 넘긴다.
// - 워커 스레드(들)가 큐에서 작업을 꺼내 처리하고, 큐가 비면 condition_variable 로 대기한다.
// - 생산자(여러 IO/로직 스레드)가 enqueue 로 작업을 넣고 워커를 깨운다.
// - 워커는 한 개일 수도, 여러 개일 수도 있다.
//
// 요구사항:
// - 워커는 큐에 작업이 있을 때만 꺼내야 한다(빈 큐에서 꺼내면 안 된다).
// - 깨어났다고 해서 항상 처리할 작업이 있다고 가정하면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 워커가 여러 개이거나 대기에서 깨어났을 때, 또는
// enqueue 와 wait 의 시점이 엇갈렸을 때 q 에 무슨 일이 일어날 수 있는지 설명하고,
// 수정안과 더 나은 설계를 제시하라. (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
struct Job { std::function<void()> run = []{}; };
class JobQueue {
public:
// 생산자: 작업을 넣고 워커를 깨운다
void enqueue(Job job) {
{
std::lock_guard<std::mutex> lk(_m);
_q.push(std::move(job));
}
_cv.notify_one(); // (A) 대기 중인 워커 하나를 깨움
}
// 워커 스레드 루프
void workerLoop() {
while (true) {
Job job;
{
std::unique_lock<std::mutex> lk(_m);
if (_q.empty()) // (B) 비었으면 대기
_cv.wait(lk); // (C) 깨어나면 진행
job = std::move(_q.front()); // (D) 꺼내서 처리
_q.pop();
}
job.run();
}
}
private:
std::queue<Job> _q;
std::mutex _m;
std::condition_variable _cv;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.