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#
내 답안 · 자동 저장

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