모니터로 관리하는 동시성 제어와 조건 동기화

모니터의 캡슐화와 자동 상호 배제, 조건 변수, 진입·조건·신호제공자 큐의 동작을 세마포어와 비교해 정리한다.

2026-08-14 · 최초 발행 2025-12-25

공유 데이터 접근을 모듈 안으로 묶는 방식

모니터(Monitor)는 공유 데이터와 critical section을 함께 감싼 프로그래밍 언어 수준의 동시성 제어 구조다. 세마포어에서 발생할 수 있는 타이밍 오류와 구현 복잡성을 줄이기 위해 쓰이며, 모니터 내부의 상호 배제는 자동으로 보장된다.

세마포어가 저수준 OS 제공 기능이라면 모니터는 언어 차원의 고수준 추상화다. Java의 synchronized, C++의 std::mutex + condition_variable, Python의 threading.Condition처럼 언어와 라이브러리에서 구현할 수 있다. 조건 변수는 특정 조건을 기다리거나 신호를 전달하는 수단이 된다.

모니터 안에 배치되는 데이터와 프로시저

공유 데이터는 모니터 내부에 두고, 외부 프로세스는 그 데이터를 조작하는 프로시저(메서드)를 통해 접근한다. 생성 시 실행되는 초기화 코드와 조건 변수 역시 모니터의 구성 요소다. 이 구조는 데이터 직접 접근을 제한하고 내부 구현 변경이 외부에 미치는 영향을 줄인다.

공유 데이터모니터프로시저 1프로시저 2프로시저 3상호 배제보장조건 변수wait/signal

Java에서는 synchronized 메서드와 wait(), notifyAll()을 이용해 버퍼의 생산과 소비를 제어할 수 있다.

public class BoundedBuffer {
    private int[] buffer;
    private int count = 0;

    public synchronized void produce(int item) {
        while (count == buffer.length) {
            wait();  // 버퍼가 가득 차면 대기
        }
        buffer[count++] = item;
        notifyAll();  // 대기 중인 consumer 깨움
    }

    public synchronized int consume() {
        while (count == 0) {
            wait();  // 버퍼가 비어 있으면 대기
        }
        int item = buffer[--count];
        notifyAll();  // 대기 중인 producer 깨움
        return item;
    }
}

대기 상태를 나누어 관리하는 큐

모니터는 최소 3개 이상의 큐를 유지한다. 각 큐는 모니터 진입을 기다리는 프로세스, 특정 조건을 기다리는 프로세스, signal()을 실행한 프로세스를 구분한다.

진입을 기다리는 프로세스

진입 큐(Entry Queue)는 모니터 내부 프로시저에 들어가기를 기다리는 프로세스를 관리한다. 모니터 내 프로시저 수만큼 존재하며, 상호 배제 규칙에 따라 한 번에 하나의 프로세스만 진입할 수 있다.

프로세스 1진입프로세스 2프로세스 3모니터프로시저 진입 번에하나만 진입

조건이 충족될 때까지의 대기

조건 큐(Condition Queue)는 조건 변수마다 존재하며, 특정 조건이 만족될 때까지 프로세스를 대기시킨다. wait()는 현재 프로세스를 조건 큐에 넣어 대기시키고, signal()은 조건 큐에 있는 한 프로세스를 깨운다.

조건 큐모니터프로세스조건 큐모니터프로세스alt[조건 불만족][조건 만족]모니터 진입조건 확인wait() 호출프로세스 대기작업 수행signal() 호출대기 프로세스 깨움

signal() 이후의 순서

신호제공자 큐(Signaler Queue)는 모니터당 1개이며 항상 존재한다. signal()을 실행한 프로세스는 임시로 이 큐에서 대기한다. 모니터 안에는 한 번에 하나의 프로세스만 있을 수 있기 때문이다.

큐 선택 우선순위는 Signaler Queue, Condition Queue, Entry Queue 순서다.

1순위2순위3순위모니터비어 있음어느 큐에서선택?Signaler QueueCondition QueueEntry Queue

P/V 호출을 직접 다루는 경우와의 차이

세마포어는 P()와 V() 호출의 순서를 개발자가 직접 관리한다. 해제를 잊거나 순서를 바꾸면 데드락 또는 상호 배제 실패로 이어질 수 있고, 동기화 코드가 여러 위치에 흩어진다.

// 잘못된 사용 예시
P(mutex);
// Critical Section
// V(mutex) 호출 잊음 → 데드락!

// 순서 잘못
V(mutex);  // Unlock 먼저
P(mutex);  // Lock 나중에 → 상호 배제 실패!
항목 세마포어 모니터
추상화 수준 저수준 (OS 제공) 고수준 (언어 제공)
상호 배제 프로그래머가 명시 (P/V) 자동 보장 (컴파일러)
타이밍 오류 발생 가능 불가능 (캡슐화)
코드 분산 여러 곳에 분산 모니터 내 집중
정보 은폐 없음 공유 데이터 직접 접근 불가
사용 편의성 어려움 쉬움

세마포어에서는 임계 구역 앞뒤에 직접 잠금과 해제를 작성한다.

semaphore mutex = 1;

void critical_function() {
    P(mutex);
    // Critical Section
    V(mutex);
}

모니터에서는 동기화 대상 메서드가 상호 배제를 자동으로 보장한다.

public synchronized void critical_function() {
    // Critical Section
    // 자동으로 상호 배제 보장
}

조건 변수로 생산과 소비를 맞추기

조건 변수는 조건 대기와 신호 전달에 쓰인다. wait()는 조건이 만족될 때까지 대기시키고, signal()은 조건이 만족됐을 때 대기 프로세스를 깨운다. broadcast()는 모든 대기 프로세스를 깨운다.

생산자-소비자 상황에서는 버퍼가 가득 찼을 때 생산자를, 비었을 때 소비자를 대기시킬 수 있다.

synchronized void produce() {
    while (full) wait();    // 버퍼 가득 찰 때 대기
    // 생산
    notifyAll();            // 소비자 깨움
}

synchronized void consume() {
    while (empty) wait();   // 버퍼 비었을 때 대기
    // 소비
    notifyAll();            // 생산자 깨움
}

모니터는 공유 데이터의 직접 접근을 막고, 프로시저를 유일한 접근 경로로 둔다. 이 캡슐화와 자동 상호 배제, 조건 변수의 조합이 동시성 제어의 안전성과 유지보수성을 뒷받침한다.

모니터동기화운영체제세마포어조건 변수