Monitor

프로세스 동기화에서 **모니터(Monitor)**는 공유 자원에 대한 안전한 접근을 보장하기 위한 상위 수준의 동기화 도구이다.
모니터는 공유 데이터와 해당 데이터를 조작하는 연산을 하나의 모듈로 캡슐화하여, 다중 스레드 환경에서의 경쟁 조건(Race Condition)을 방지한다.

모니터는 고수준의 동기화 추상화로, 복잡한 뮤텍스/세마포어 관리 없이 안전한 병행 프로그래밍을 가능하게 한다.
현대 언어에서는 모니터 패턴이 내장되어 있어(synchronized, lock), 데드락과 경쟁 조건을 효과적으로 방지한다.
다만 저수준 시스템 프로그래밍에서는 뮤텍스나 세마포어가 더 유연할 수 있다.

정의

모니터는 **뮤텍스(Mutex)**와 **조건 변수(Condition Variable)**를 결합한 추상화된 동기화 메커니즘이다.

  • 뮤텍스: 모니터 내부에서 한 번에 하나의 스레드만 진입하도록 보장한다.
  • 조건 변수: 특정 조건이 충족될 때까지 스레드를 대기시키거나 알림을 보낸다(wait()signal()).

구성 요소

모니터는 다음과 같은 요소로 구성된다:

  • 공유 데이터: 여러 스레드가 접근하는 변수.
  • 모니터 프로시저(Procedure): 공유 데이터를 조작하는 메서드.
  • 초기화 코드: 모니터 생성 시 데이터 초기화.
  • 진입 큐(Entry Queue): 모니터 진입 대기 스레드의 큐.
  • 조건 변수 큐: wait() 호출로 대기하는 스레드의 큐 (예: notEmpty, notFull).
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class SimpleMonitor:
    def __init__(self):
        self._lock = threading.Lock()
        self._condition = threading.Condition(self._lock)
        self.shared_resource = 0
    
    def synchronized_method(self):
        with self._lock:  # 상호배제 보장
            # 임계 영역 코드
            self.shared_resource += 1
    
    def wait_for_condition(self):
        with self._condition:
            while not self.some_condition():
                self._condition.wait()
    
    def signal_condition(self):
        with self._condition:
            self._condition.notify()

모니터의 동작 원리

  1. 상호 배제(Mutual Exclusion)
    모니터 내부에서는 한 번에 하나의 스레드만 실행된다.
    다른 스레드는 진입 큐에서 대기한다.

  2. 조건 변수(Condition Variable)

    • wait(): 조건이 충족되지 않으면 스레드를 대기 상태로 전환하고 모니터 락을 해제한다.
    • signal(): 대기 중인 스레드 하나를 깨워 실행을 재개한다.

모니터의 장단점

장점

  • 캡슐화: 공유 데이터와 동기화 로직을 하나의 모듈로 통합.
  • 안전성: 뮤텍스와 조건 변수를 직접 사용하는 것보다 오류 가능성이 낮음.
  • 간결성: 세마포어보다 직관적인 코드 작성 가능.

단점

  • 언어 의존성: Java, C# 등 특정 언어에서만 지원.
  • 컴파일러 부담: 모니터 구현을 위해 컴파일러가 추가 작업 필요.

모니터의 실제 구현 예시

  1. 생산자-소비자 문제

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    class BoundedBuffer:
        def __init__(self, size):
            self._monitor = threading.RLock()
            self._not_full = threading.Condition(self._monitor)
            self._not_empty = threading.Condition(self._monitor)
            self.buffer = collections.deque(maxlen=size)
    
        def produce(self, item):
            with self._monitor:
                while len(self.buffer) == self.buffer.maxlen:
                    self._not_full.wait()
                self.buffer.append(item)
                self._not_empty.notify()
    
        def consume(self):
            with self._monitor:
                while len(self.buffer) == 0:
                    self._not_empty.wait()
                item = self.buffer.popleft()
                self._not_full.notify()
                return item
    
  2. 읽기-쓰기 문제

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
    class ReadWriteMonitor:
        def __init__(self):
            self._monitor = threading.Lock()
            self._no_readers = threading.Condition(self._monitor)
            self._no_writers = threading.Condition(self._monitor)
            self.readers = 0
            self.writers = 0
    
        def start_read(self):
            with self._monitor:
                while self.writers > 0:
                    self._no_writers.wait()
                self.readers += 1
    
        def end_read(self):
            with self._monitor:
                self.readers -= 1
                if self.readers == 0:
                    self._no_readers.notify()
    
        def start_write(self):
            with self._monitor:
                while self.readers > 0 or self.writers > 0:
                    self._no_readers.wait()
                    self._no_writers.wait()
                self.writers += 1
    
        def end_write(self):
            with self._monitor:
                self.writers -= 1
                self._no_writers.notify_all()
    
  3. Java의 synchronized

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    public class Counter {
        private int count = 0;
    
        public synchronized void increment() {
            count++;
            notifyAll();  // 대기 중인 스레드에 알림
        }
    
        public synchronized int getCount() {
            while (count == 0) {
                try { wait(); } 
                catch (InterruptedException e) {}
            }
            return count;
        }
    }
    
    • synchronized 키워드로 메서드 전체를 동기화.
  4. C#의 Monitor 클래스

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    using System.Threading;
    class Example {
        private object lockObj = new object();
    
        public void ThreadSafeMethod() {
            Monitor.Enter(lockObj);
            try {
                // 크리티컬 섹션
            }
            finally {
                Monitor.Exit(lockObj);
            }
        }
    }
    
    • Enter()Exit()으로 명시적 락 관리.

참고 및 출처