CS/Operating System

10. 모니터(Monitor)

공부중인학생 2023. 12. 6. 19:17

세마포어는 P, V 오퍼레이션을 fair하지 않을 때 문제가 발생하며 소프트웨어 툴이 발견할 수 없다는 측면에서 race condition 버그를 자주 발생시킵니다. 대부분의 경우 하나의 프로세스에서 발생하지 않고 여러 개의 프로세스들이 유기적으로 동작할 때 나타나는 경우가 많습니다.

 

이런 문제를 해결하기 위한 structured sychronization preemptive가 모니터(Monitor)입니다. 

 

 

 

세마포어가 너무 로우레벨이여서 버그를 발생하는 일이 자주 일어납니다. 하이레벨로 코딩을 할때는 자동적으로 synchronization을 해주는 모니터가 존재합니다. 

 

 

 

이전에 배운 spin lock이나 disable interrupt는 하드웨어적인 preemptive이고 세마포어는 OS가 제공해주는 소프트웨어적인 preemptive 였습니다. 프로그래밍 랭귀지에서 모니터를 제공하려면 Abstract Data Type(ADT)라는 개념을 활용합니다. ADT는 객체지향언어의 클래스 같은 것으로 내부 데이터 구조에 대한 오퍼레이션을 API 형태로 제공해줍니다.

 

 

 

모니터는 shared data에 대한 access를 조절하는 기능을 제공해주며 ADT나 클래스의 형태를 띕니다. 클래스와 같이 멤버 변수가 있고 멤버 변수에 대한 조작을 가할 수 있는 내부 함수 등이 존재하는 구조.

 

클래스와 모니터의 다른 점은 모니터의 경우 public function을 호출했을 때 그 함수들이 critical section입니다. 한 쓰레드가 호출한 모니터 함수가 수행이 되면 다른 쓰레드의 모니터 함수 호출이 지연됩니다. 이렇게 public function 단위로 critical section을 제공해주는 것이 모니터 입니다.

 

모니터를 사용하면 synchronization 문제가 발생할만한 곳에 컴파일러가 적절히 세마포어를 추가하기 때문에 프로그래머가 따로 세마포어 코딩을 진행하지 않아도 됩니다. 

 

 

 

모니터의 signaler의 동작에 대한 의미는 호어와 메사의 방법으로 나뉘게 됩니다. 호어의 시멘틱스에서는 signaler가 어떤 조건이 만족되면서 waiting하고 있는 프로세스를 깨우고 그 프로세스가 바로 critical section에 들어갑니다. 이후 signaler가  critical section에서 빠져나와 queue에서 대기하는 모습을 띄게 됩니다. (condition variable과 유사)

 

메사의 시맨틱스에서는 signaler가 waiting 프로세스를 깨우더라도 바로 들어오지 않고 대기하다 signaler가 수행을 마치고 종료하면 그때 들어가는 방식으로 동작합니다.

 

 

 

간단한 ADT를 가지고 모니터의 동작을 알아보겠습니다.  위 사진에서는 프로듀서와 컨슈머 쓰레드의 함수가 존재하며 함수 부분이 critical section으로 표시가 되어있지 않습니다.

 

먼저 프로듀서가 데이터를 생성하면 모니터 ADT의 ProducerConsumer.intert 함수를 통해서 데이터를 추가합니다. 컨슈머는 ProducerConsumer.remove 함수를 통해서 데이터를 얻어옵니다.

 

ProducerConsumer라는 이름을 가진 모니터 클래스에서 insert와 remove 함수를 사용하여 버퍼와 데이터를 다룹니다. 

 

 

 

이번에는 full과 empty 이렇게 두 개의 condition variable을 사용하는 예시입니다. 이 두 변수는 버퍼가 꽉 찼다라는 정보와 버퍼가 비워져 있다라는 정보를 저장합니다. 버퍼의 사이즈가 N으로 꽉 찼다면 full이라는 변수에 waiting 함수를 호출해서 프로듀서 쓰레드를 waiting 시킵니다.

 

프로듀서 쓰레드가 깨어난 경우 빈 공간이 있다는 의미니 버퍼에 데이터를 넣습니다. if 문에서는 empty 상태일 땐 기다리고 있는 컨슈머를 깨워주는 동작을 진행합니다. 

 

 

 

이 코드에는 busy와 x라는 컨디션 변수가 존재합니다. 모니터에는 두 개의 함수가 있는데 첫 번째가 리소스를 사용하기 위해 호출하는 acquire 함수이고 두 번째가 리소스를 반환하는 release 함수입니다. 동작은 acquire를 호출, busy가 true면 사용할 수 있는 리소스가 없으므로 waiting을 하고 false이면 busy를 true로 바꾸고 리소스를 사용합니다.

 

그 이후 release를 사용하여 리소스를 반환하고 busy를 false로 변환합니다. 이때 acquire 단계에서 waiting하고 있는 프로세스가 있다면 깨워줘야 하는데 이 역할을 x.signal이 해줍니다. 마지막으로 busy의 초기값은 false로 바꾸며 동작이 마무리 됩니다.

 

 이런 모니터 코드를 컴파일러가 컴파일하면 세마포어의 P, V 오퍼레이션이 어디서 사용되는지 알 수 있습니다.

 

 

 

두 번째 모니터에는 acquire과 release라는 두 개의 함수가 존재했습니다. 모니터 함수의 바디는 critical section이니 함수의 시작부분에서 P오퍼레이션, 함수의 끝 부분에서 V 오퍼레이션을 반드시 넣어줘야 합니다. 

 

컴파일러의 동작은 코드 내에서 프로세스들이 공유하는 리소스를 전부 도출하고 각 리소스 마다 한 개의 세마포어를 할당하는 방식으로 진행됩니다. 프로듀서-컨슈머의 경우 버퍼와 버퍼안에 있는 데이터가 공유 리소스이기 때문에 2개의 세마포어가 필요하다는 것을 알 수 있습니다.

 

하지만 모니터의 경우 공유 리소스를 도출하는 것이 어렵습니다. 그렇기 때문에 큐를 도출하는 방식으로 진행하면 더 수월하게 세마포어가 쓰이는 위치를 알 수 있습니다. 어떤 리소스를 사용하고자 할 때 lock을 얻지 못한 프로세스는 waiting queue에서 대기하기 때문에 공유 리소스마다 별도의 waiting queue를 하나씩 가집니다. 이 queue들의 개수를 알 수 있다면 사용되는 세마포어의 개수를 알 수 있습니다. (여기서 3개의 lock이 보이 듯이 3개의 queue가 존재함을 알 수 있습니다.)

 

모니터는 사용자가 리소스 쉐어링 문제를 고민하지 않게 해주지만 모든 프로그래밍 언어에서 지원하지 않습니다.(C언어 같이 클래스가 없는 경우), 지원되지 않을 때는 pthread의 세마포어와 컨디션 변수를 사용하여 동작을 직접 만들어줘야 합니다.