Monitor

EXEM Knowledge Base

Jump to: navigation, 찾기

자바는 Language Level로 multi-threading을 구현하였다. 그렇기 때문에 Thread들 사이의 데이터 접근이나 공유를 위해 synchronization이라는 방법을 사용한다. 이 Synchronization을 구현하기 위해 자바는 Monitor를 사용한다.

Monitor는 아래의 그림 중 초록색 방과 같다. 특정 데이터를 이용하기 위해서는 이 방에서만이 가능하다. 그러나 이 방의 공간은 Thread가 딱 하나 밖에 들어 갈 수 없기 때문에 어떤 Thread가 들어가게 되면 이 Thread가 나올 때 까지는 누구도 이 방에 들어갈 수 없다. 그렇기 때문에 이 방에 들어가 있는 Thread는 자신이 특정 데이터를 가지고 작업을 하는데 있어 독립성을 보장 받게 된다. 이는 마치 Oracle에서 특정 resource를 사용하기 위해 획득하는 lock이나 latch와 같다고 할 수 있다.

 그림:Monitor1.png

Thread 가 monitor로 보호되는 resource를 이용하기 위해서는 monitor를 획득해야만 한다. 이 monitor를 획득하기 위해서는 Entry Set에 진입해야 하는데 만약 Monitor를 획득한 Thread도 없고 Entry Set도 비어 있다면 Monitor를 바로 획득할 수 있지만 누군가 Monitor를 획득한 상태로 작업을 하고 있을 때는 Entry Set에서 대기해야만 한다. Monitor를 소유한 thread가 작업을 마치고 Monitor를 놓게 되면 Entry Set에서 대기하고 있는 Thread들은 서로 Monitor를 획득하기 위해 경쟁해야 하고 최종 승자가 Monitor를 획득하게 된다. Wait Set은 뒤에 가서 설명하도록 한다.

Monitor는 두 가지 종류의 Synchronization을 위해 사용되는데 하나는 mutual exclusion이고 또 다른 하나는 cooperation이다. Mutual exclusion은 공유 데이터에 대해 각각 그 작업이 보장되게끔 Thread들의 독립성을 보장해 주는 것이고 cooperation은 같은 목적을 가진 Thread들이 각기 어우러져 작업을 할 수 있도록 하는 것을 의미한다. 여기서 전자는 object lock를 이용하고 있고 후자는 Object class의 wait, notify method를 이용한다.

Mutual Exclusion은 단순하게 특정 monitor를 상호 배타적으로 사용하는 것을 의미한다. 일반적으로 이 Mutual Exclusion은 다수의 thread가 데이터를 공유할 때 서로 간섭 없이 사용하게 될 때 꼭 필요하다. JVM은 time slice를 구현하지 않았기 때문에 우선순위가 높은 thread의 경우는 상대적으로 우선순위가 낮은 thread들 사이에서 아무런 간섭도 받지 않게 된다. 그래서 우선순위가 높은 Thread가 CPU를 독점하는 현상이 발생하고 이에 따라 우선순위가 낮은 thread는 CPU를 획득하지 못해 기아에 허덕이게 된다. 이럴 경우 monitor는 모든 thread가 골고루 기회를 가질 수 있도록 조정하는 역할을 하게 된다.

Cooperation은 한 thread가 특정 상태의 어떤 데이터들을 필요로 하고 다른 Thread는 같은 상태로 데이터를 가져와야 하는 경우에 사용된다. 예를 들면 한 Thread가 buffer에서 데이터를 읽어오는 작업을 하는데 이때 buffer는 'not empty'상태여야만 한다. 만약 'empty'상태라면 이 thread(read thread)는 대기해야만 한다. 다른 thread(write thread)는 buffer에 데이터를 채워 넣는 작업을 한다. 그러므로 이 write thread는 buffer를 'not empty'상태로 만들어 주는 역할을 하게 된다. Write thread가 buffer에 어떤 내용을 기록할 때 read thread는 JVM이 'Wait and Notify'라고 명명한 monitor를 이용할 수 있다.

이 monitor는 다음과 같이 동작한다. Monitor를 소유한 thread는 wait command를 실행하여 자신을 suspend상태로 만든다. Thread가 wait 상태가 되면 monitor를 놓고 wait set으로 들어가게 된다. 이 thread는 monitor를 대신 가지고 있는 thread가 notify command를 수행할 때까지 wait set에서 대기하게 된다. Notify를 실행한 thread는 하던 일을 마무리하고 monitor를 놓을 때까지 계속해서 monitor를 소유하게 된다. (그래서 이 monitor를 Signal and Continue monitor라고도 한다.) 이 thread가 monitor를 놓게 되면 지금까지 대기하고 있던 thread는 다시 깨어나 monitor를 재획득하게 된다.

앞의 예를 가지고 얘기를 하면 read thread가 monitor를 획득하고 buffer의 상태를 체크해 보아 'not empty' 상태이면 buffer를 읽고 나서 작업이 완료되면 monitor를 놓게 된다. 이와 반대로 'empty' 상태인 경우 monitor를 놓고 wait set으로 들어가게 된다. 이후 Write thread는 monitor를 획득하고 난 후 buffer를 채우고 notify를 실행한다. Write thread가 notify를 수행할 때 read thread 는 깨어나기 위해 making이 된다. write작업이 끝나면 write thread는 monitor를 놓게 되고 buffer는 'not empty'상태가 된다. 그러면 read thread는 다시 monitor를 재획득하여 buffer의 상태를 check하고 여전히 'not empty'상태라면 buffer의 내용을 읽는 작업을 하고 난 후 monitor를 놓고 빠져나간다. 그런데 write thread가 monitor를 놓을 때 read thread 외의 다른 thread가 monitor를 획득할 수 도 있다. 그렇게 되면 buffer는 다시 'empty'상태가 될 수 있기 때문에 read thread는 다시 buffer의 상태를 체크하게 되는 것이다. 결국 write thread가 nofity해 주는 것은 read thread가 원하는 상태가 되었다는 확신이 아니라 그 상태일 것이라는 힌트 정도에 불과한 것이다.

그림:Monitor2.png

위의 그림은 Monitor의 동작 과정을 간략하게 나타낸 것이다. 한 thread가 monitor로 보호되는 데이터에 접근하기 위해 ①번과 같이 Entry Set에 진입한다. 만약 현재 monitor를 소유한 Thread도 없고 entry set도 비어 있다면 이 thread는 ②번과 같이 monitor를 획득하게 된다. 이 thread가 monitor를 소유하고 있는 동안 다른 thread가 동일 데이터에 접근한다면 이 thread는 entry set에서 대기 해야만 한다. 이 대기하는 thread는 blocked상태가 되며 monitor를 소유하고 있는 thread가 monitor를 놓은 후 monitor를 획득할 때까지 이 상태를 유지하게 된다. 위의 그림에서 보면 3개의 thread가 blocked상태로 대기 중인 것을 알 수 있다.

Monitor를 획득한 thread 즉 active thread가 monitor를 놓는 경우는 두 가지 이다. 하나는 작업이 완료된 경우이고 다른 하나는 wait command를 실행한 경우이다. 전자의 경우는 ⑤번과 같이 monitor를 놓고 나가버린다. 후자의 경우는 ③번 처럼 wait set으로 들어가게 된다. Active thread가 monitor를 놓기 전에 notify를 하지 않게 되면 entry set에 있는 thread들만이 monitor를 놓고 경쟁한다. Notify command를 수행하면 entry set과 wait set에 있는 모든 thread들이 경쟁하게 된다. 만약 wait set의 thread가 경쟁에서 승리하면 ④번의 경로를 거쳐 monitor를 획득하게 된다. Entry set의 thread가 경쟁에서 획득하게 되면 당연히 ②번의 경로 monitor를 획득하게 된다. Thread가 wait set에 있으면 waited 상태가 되는데 이것은 thread가 active 상태에서만 wait command를 수행할 수 있고 그렇게 되어야 wait set으로 들어가 waited상태가 될 수 있다. 또한 wait set에 진입한 thread의 경우 monitor를 획득하는 방법 외에는 wait set에서 벗어날 수는 없다.

Thread가 wait command를 수행할 때 timeout을 설정할 수 있다. Timeout을 설정한 경우 이 timeout이 끝나기 전까지는 어느 thread도 이 wait thread에게 notify를 실행할 수 없다. Timeout이 지나면 JVM이 자동적으로 notify를 수행하게 되고 notify command가 수행되지 않아도 waiting thread는 깨어나게 된다.

지금까지 monitor에 대해서 개략적으로 살펴보았다. 여기서 빠진 부분은 경합 상황에서 thread를 선택하는 방법인데 이것은 JVM을 구현하는 사람들의 몫이다. 이를 단순히 Queue를 사용할 수도 있고 Priority별 Queue로 구현할 수도 있고 stack식으로 LIFO방식을 사용할 수도 있다. 그러므로 이에 대한 자세한 정보는 각 JVM벤더를 통해야 할 것으로 보인다.


[편집] 참고자료

INSIDE THE JAVA VIRTUAL MACHINE, Bill Venners, McGraw-Hill, 2000