게시:
수정:

모니터(Monitor)란?

세마포어(Semaphore)와 같은 추상적인 개념으로서, 동시 수행(Concurrency) 중인 프로세스 사이에서 추상형 데이터 타입(Abstract data type)의 안전한 공유를 보장하기 위하여 언어(Language) 수준에서 지원하는 High level synchronization construct이다.

유용성

세마포어가 비교적 쉽게 Critical section에 접근할 수 있도록 돕긴 하지만, 프로그래머가 코딩을 잘못하여 버그(Bug)가 생기면 검증이 어려운. 즉, 정확성(Correctness) 입증이 어려운 단점이 있다.

예를 들어 P(), V() 연산의 순서가 뒤바뀌거나(Mutex issue), P() 연산 후 V() 연산을 해야 하는데 다시 한 번 P() 연산(Deadlock issue)을 하는 등 단 한 번의 실수라도 있을 경우 시스템에 치명적인 영향을 준다.

이와 같이 코딩이 어렵기 때문에 자체적으로 공유 자원의 접근을 차단하는 모니터가 유용한 것이다.

구성과 특징

모니터 Struct는 다음과 같은 Pseudo code로 표현할 수 있다.

monitor Monitor-name
{  shared variable declarations /* Condition variable. 프로시저로만 접근 가능 */
   condition variable x, y;     /* 프로세스가 모니터 안에서 Waiting 할 수 있도록 함 */
   
   procedure body P1(...) {    /* 모니터 안에서 정의된 프로시저 */
    ...
   }
   
   procedure body P2(...) {
    ...
   }
   
   procedure body Pn(...) {
    ...
   }
   {
       initialization code
   }
} 

모니터 안에는 공유 데이터(Shared data), 컨디션 변수(Condition variable) 및 공유 데이터에 접근하는 프로시저(Procedure)가 정의되어 있다. 공유 데이터에 접근하려는 모든 프로세스는 모니터 내의 프로시저를 통해서만 접근할 수 있으며 동시에 여러 개의 프로시저가 실행될 수 없다.

Monitor
모니터(Monitor)의 구성

컨디션 변수(Condition variable)는 세마포어 변수와 같은 역할을 하지만 세마포어는 값을 가지는 반면 컨디션 변수는 값을 갖지 않고, 만약 자원이 없어서 대기해야 하는 상황이라면 자신의 Queue에 프로세스를 매달아서 Sleep(wait()) 시키거나, Wake-up(signal()) 하는 역할을 한다는 차이점이 있다.

  • wait(): 어떠한 프로세스가 어떠한 조건에 만족하지 못할 때(예: 자원이 없을 때), 다른 프로세스가 Invoke하기 전까지 Suspend시킨다. Block & Wake-up 방식에서 Block에 해당한다.
  • signal(): 정확하게 하나의 Suspend된 프로세스를 Resume 시킨다. 만약 Suspend된 프로세스가 없으면 아무일도 일어나지 않는다. Block & Wake-up 방식에서 Wake-up에 해당한다.

모니터를 활용할 때 얻는 이점으로는 공유 데이터에 접근 가능한 프로시저가 모니터 내에서 동시 실행될 수 없기 때문에 굳이 Lock을 걸 필요가 없어지므로 프로그래머 입장에서 Lock/Unlock에 대한 고민이 사라진다는 점이다. 제약 조건을 명시적으로 코딩할 필요가 없어 코딩이 간단해진다.

세마포어(Semaphore)와 비교

Semaphore
Monitor
프로그래머가 자원을 얻거나 반납하기 위해 P(), V() 연산을 해줘야 한다. Lock / Unlock을 걸 필요가 없다.
Mutual exclusion 조건을 직접 구현해야 한다. Mutual exclusion 조건을 모니터 차원에서 지원한다.
(공유 자원 동시 접근 차단을 모니터 차원에서 지원한다.)
세마포어 변수는 값을 가진다. 컨디션 변수는 값을 갖지 않는다.

문제에 적용해보자

이전 포스팅의 Classic problem of synchronization 문제 3가지 중, Bounded-Buffer Problem(Producer-Consumer Problem)과 Dining-Philosophers Problem에 적용해보자.

Bounded-Buffer Problem

monitor bounded_buffer
{    int buffer[N];
     condition full, empty;  /* 내용이 비어있거나 들어 있기를 기다리며 줄세워 놓는 변수 */
    
     void produce(int x)
     {   if there is no empty buffer  /* 빈 버퍼가 없으면, */
             empty.wait();            /* Queue에서 sleep */
         add x to an empty buffer
         full.signal();               /* 혹시 기다리고 있을 consumer process resume */
     }
    
     void consume(int *x)
     {   if there is no full buffer   /* 버퍼가 텅 비어 있으면, */
             full.wait();             /* Queue에서 sleep */
         remove an item from buffer and store it to *x
         empty.signal();              /* 혹시 기다리고 있을 producer process resume */
     }
}

여기서 생산자(Producer) 프로세스는 공유 버퍼(Buffer)에 데이터를 만들어서 집어넣는 프로세스이고, 소비자(Consumer) 프로세스는 공유 버퍼에서 데이터를 꺼내가는 포로세스이다.

세마포어 코드에서는 생산자 프로세스가 버퍼에 데이터를 넣으려면 버퍼 전체에 락(Lock)을 걸어서 다른 생산자 프로세스나 소비자 프로세스가 접근 못 하게 막은 뒤 데이터를 넣어야 했고, 소비자 프로세스가 버퍼에서 데이터를 꺼낼 때에도 마찬가지로 버퍼 전체에 락을 걸어서 다른 프로세스의 접근을 차단하고 데이터를 꺼내가도록 구현했었다.

그런데 모니터에서는 공유 버퍼에 접근할 때 생사자 프로세스든 소비자 프로세스든 코드를 실행하는 도중에 다른 프로세스가 접근하는 것을 모니터가 막아주기 때문에 공유 버퍼에 대해서 락을 걸거나 락을 푸는 코드가 필요 없어졌다.

예를 들어 생산자 프로세스가 데이터 x를 생산해서 버퍼에 넣으려는 경우, 버퍼에 빈 공간이 없으면 빈 버퍼를 기다리는 Queue에서 잠들게(empty.wait()) 하고, 그렇지 않으면 데이터를 추가한 뒤에 혹시 데이터가 있는 버퍼를 기다리며 Sleep 중인 소비자 프로세스를 꺠우게(full.signal()) 된다.

추가적으로 컨디션 변수는 값이 아니기 때문에 full.signal()(또는 empty.signal()) 했을 때 만약 기다리는 소비자 프로세스(또는 생산자 프로세스)가 없어도 아무 일도 일어나지 않는다.

Dining Philosophers Problem

세마포어로 구현한 코드와 거의 유사하다. Bounded-Buffer Problem와 마찬가지로 세마포어 코드에는 없는 If statement가 있고, 락(Lock)을 걸고 푸는 과정이 없는 차이점이 있다.

Each Philosopher:
{    pickup(i);
     eat();
     putdown();
     think();
} while(1)


monitor dining_philosopher
{
     enum {thinking, hungry, eating} state[5];
     condition self[5];
     
     void pickup(int i) {
         state[i] = hungry;
         test(i);
         if (state[i] != eating)
             self[i].wait();         /* wait here */
     }
     
     void putdown(int i) {
         state[i] = thinking;
                                     /* test left and right neighbors */
         test((i+4) % 5);            /* if L is waiting */
         test((i+1) % 5);
     }
     
     void test(int i) {
         if ((state[(i+4) % 5] != eating) &&
             (state[(i+1) % 5] != eating) &&
             (state[i] == hungry)) {
                 state[i] = eating;
                 self[5].signal();   /* wake up Pi */
         }
     }
     
     void init() {
         for (int i=0; i<5; i++)
             state[i] = thinking;
     }
}

Reference

반효경, “반효경 [운영체제] 14. Process Synchronization 3”. KOCW. 2014년 4월 8일. video, http://www.kocw.net/home/cview.do?lid=c5e572cd1319ca6b
반효경, “반효경 [운영체제] 15. Process Synchronization 4”. KOCW. 2014년 4월 11일. video, http://www.kocw.net/home/cview.do?lid=3860d0b9372331de


OS 시리즈 모두보기 (펼치기)


댓글남기기