본문 바로가기

Concurrency

뮤텍스(Mutex) VS 모니터(Monitor)

간단하게 말해서 Monitor 는 Mutex + Condition Variable 이다.

 

Monitor 의 이해를 돕기 위해 아래 코드를 살펴보자.

 

아래 코드를 보면 predicate 라는 부분이 나오는데, 이 부분은 스레드간 선제조건을 의미한다.

즉, 이 값이 true 가 되었다는 것은 이전에 인과의 관계에 속해있는 사전 작업이 완료되었다는 것을 의미하고 그때 현재 스레드의 작업을 수행할 수가 있다.

 

void example() {
    // acquire mutex
    while (predicate is false) {
        // release mutex
        // acquire mutex
    }

    // do something in this thread.
    // release mutex
}

 

 

위 코드의 flow 를 보면,

  1. 먼저 mutex 를 현재 스레드가 획득한 다음,
  2. predicate 조건을 확인하고 만약 조건이 아직 처리되지 않았다면, mutex 를 release 함으로써 다른 스레드에게 mutex 를 획득할 수 있는 기회를 준다. 그리고 또 다시 mutex 를 획득하고 predicate 조건을 확인하는 일을 반복한다.
  3. 그리고 만약에 predicate 를 통과했다면 이 스레드가 해야할 일을 하고 mutex 를 release 해준다.

그런데 위 코드를 보면 스레드가 predicate 를 체크하는 도중에 계속해서 active 상태에 있는 것을 알 수 있다. 이렇게 active 상태에 있게되면 CPU 자원을 계속해서 소모하게 되어 spin waiting 현상을 유발하게 된다.

Condition Variables 를 활용한 spin waiting 현상 해결

Condition Variables 를 활용해 위의 현상을 해결할 수 있다. 개념적으로 Condition Variables 는 각각 waitsignal() 의 두개 메소드를 가지고 있다.

 

wait() 가 호출되면 해당 스레드 A (A라 하겠다) 가 점유하고 있던 mutex 가 atomically 하게 release 된다. 또한 실행되던 스레드 A 는 wait queue 로 들어가게 된다. 그러면 wait queue 에서 대기하고 있던 또 다른 스레드 B 가 mutex 를 점유하고 predicate 를 변경하려는 시도를 할 수 있게 된다.

 

만약, 스레드 B 가 predicate 를 변경하는데 성공했다면, 이를 다시 스레드 A 에게 알려야 할것이다. 그런데 스레드 A 는 wait queue 에서 잠들어 있는 상태이다.

 

때문에 이 스레드 A 를 다시 ready 상태로 만들기 위해 signal() 메소드를 호출해준다.

signal() 메소드가 호출되게 되면 wait queue 에 있던 스레드 A 는 ready queue에 위치하게 된다. (그러나 이것이 실행중인 상태를 의미하지는 않는다.

 

단지 thread B 가 소유하고 있던 mutex 를 release 한 것이며, wait queue 에 잠자고 있던 다른 스레드들을 ready queue 에 옮김으로써 해당 mutex 를 다시 선점할 수 있는 기회를 준 것이다. 이후 실행상태로 넘어가는 것은 CPU scheduling 정책에 의해 좌우된다.)

 

그렇다면 위의 spin waiting 현상을 어떻게 해결했는지 다음 코드로 확인해보자.

 

void example() {
    // acquire mutex
    while (predicate is false) {
        conditionVariable.wait();
    }

    // do something in this thread.
    // release mutex
}

void change() {
    mutex.acquire();
    predicate = true;
    conditionVariable.signal();
    mutex.release();
}

 

위의 코드를 보면 기존에 predicate 를 확인하고 while loop 안에서 mutex 를 release 했다가 다시 점유하는 코드가 사라지고, conditionVariable.wait(); 를 호출하는 것을 볼 수 있다. (이 코드가 호출되면 현재 스레드 A 가 wait queue 로 이동하게 된다.)

 

그리고 change method 에서 또 다른 스레드가 signal 을 호출하면 ready queue 로 이동하게 되고 mutex 까지 release 되면 처음 thread A 가 다시 predicate 를 확인하게 된다.

 

여기서 주의해야할 점은 predicate 를 체크하는 구문은 if 로 바꾸어서는 안된다. predicate 를 변경하는 메소드 또한 여러 스레드에 의해 호출될 수 있기 때문이다.

 

만약에 thread B 가 predicate 를 true 로 바꾸고 thread A 가 ready queue로 옮겨졌지만 아직 실행되기 전에 thread C 라는 녀석이 predicate 를 다시 false 로 바꾸었다면 thread A 가 실행되어서 mutex 를 획득하는 시점에 다시 predicate = false 인것이 체크되어야 하기 때문이다.

Monitor

위에서 언급했듯이 Monitor = mutex + one or more condition variables 라고 정의할 수 있다. monitor 하나는 여러개의 condition variable 을 가질 수 있지만 역은 성립하지 않는다.

 

Monitor 는 두개의 queue 를 가지는 entity 또는, thread 가 위치할 수 있는 집합으로 정의할 수 있다.

  1. 최초 스레드 A 와 B 가 들어오게 되면 Entry Set 에 위치하게 된다.
  2. 그리고 이때 Monitor 를 소유하고 있는 스레드가 없다면 (즉, 모니터 영역에서 실행중인 스레드가 없다면) thread A (thread A 와 B 둘중 하나, 여기서는 A) 는 monitor 를 획득하고 실행중인 상태가 된다.
  3. thread A 가 wait() 를 호출하게 되면 다시 monitor 영역에서 나와서 wait set 에 위치하게 된다.
  4. 이때 thread B 는 monitor 에 진입할 수 있는 기회를 얻게 되어 monitor 에 진입하게 된다.
  5. 그리고 thread B 또한 wait 를 호출하게 되면 A 와 마찬가지로 wait set 영역에 위치하게 된다.
  6. 이때 thread C 가 들어온다면 entry set 에 위치했다가 바로 monitor 를 점유할 수 있게된다.
  7. 그리고 이때 thread C 가 signal 을 호출하게 되면 wait set 에 대기하고 있던 thread A,B 두개의 스레드가 깨어나서 둘중 하나의 스레드가 monitor 를 재점유할 수 있게 된다.

'Concurrency' 카테고리의 다른 글

Mutex vs Semaphore  (0) 2024.04.13
Critical Section & Race Condition  (0) 2024.04.13
Deadlock, liveness, Live lock, Starvation  (0) 2024.04.13