이전 포스팅에서 세마포어 방식을 이용한 동기화 문제 해결법에 대해 알아보았습니다. 이번 시간에는 모니터 방식에 대해 설명드리겠습니다. 세마포어와 모니터는 둘다 프로세스 동기화 문제를 프로그래머 입장에서 좀 더 쉽게 할 수 있게 해줍니다.
세마포어 방식은 단점이 있는데, 만약 한번 실수하게 되면 모든 시스템에 치명적인 영향을 준다는 것입니다.
예를 들어 Critical section에 들어가기 전 V 연산을 하고 빠져나올 때 P 연산을 하게 된다면, 2개 이상의 프로세스가 Critical section에 들어갈 수 있어 상호 배제 원칙(Mutual exclusion)이 깨지게 됩니다.
또 한가지 경우는 P 연산 후 빠져나올 때 P 연산을 한번 더 수행한다면, Deadlock에 걸려 어느 누구도 Critical section에 들어가지 못하는 문제가 생깁니다. 또 만약 프로그래머가 실수를 해서 잘 동작하지 않을 때 정확성의 검증이 어렵습니다.
이처럼 세마포어를 사용하더라도 불편한 점들이 있기 때문에 모니터 방식을 제공합니다.
1. Monitor
모니터 방식은 프로그래밍 언어 차원에서 동기화 문제를 해결하는 high-level synchronization construct라고 볼 수 있습니다.
객체 지향 프로그래밍 언어 같은 것을 보면 객체 중심으로 operation들이 정의되는 것을 볼 수 있습니다. 그런 것에 기반하여 모니터는 어떤 공유 데이터에 접근하기 위해서는 모니터라고 정의된 내부의 프로시저를 통해서만 공유 데이터에 접근할 수 있게 만들어 놓은 것입니다.
그리고 모니터가 모니터 내부에 있는 프로시저는 동시에 여러 개가 실행되지 않도록 통제 권한을 주는 것입니다.
프로그래머 입장에서는 이런 방식에서는 lock을 걸 필요가 없다는 장점이 있습니다. 세마포어는 동시 접근을 막기 위해서 항상 P 연산으로 락을 걸고 V 연산으로 락을 푸는 코드를 작성해야했지만, 모니터는 기본적으로 모니터에 대한 동시 접근을 허락하지 않습니다. 모니터 자체가 그렇게 만들어졌기 때문에 프로그래머가 락을 걸 필요 없이 모니터에 있는 공유 데이터에 접근하면 되는 것입니다.
그래서 모니터는 보통 모니터 내부에 공유 변수에 대한 선언을 해놓고, 이 공유 데이터에 접근하기 위한 프로시저들은 모니터 내부 함수로 구현을 해놓습니다.
세마포어에서는 락을 거는 것과 자원의 개수를 세는 것이 필요했습니다. 모니터에서는 락을 걸 필요는 없지만, 자원을 세는 카운팅 변수는 필요합니다. 자원이 몇 개인지를 세고 자원이 있으면 접근할 수 있게 해주고, 자원이 없으면 기다리게하는 방식은 모니터에도 여전히 필요합니다.
그걸 위해서 세마포어 변수처럼 비슷한 역할을 해주는 condition variable이라는 것이 있습니다. condition variable은 어떤 조건을 만족하지 않아서 오래 기다려야할 때 그 프로세스를 잠들게 하기 위한 용도입니다.
condition variable은 wait와 signal 연산에 의해서만 접근 가능합니다. 만약 x라는 자원이 여분이 있다면 바로 접근을 하게 해주고, 여분이 없어서 기다려야한다면 x.wait() 연산을 통해 그것을 기다리는 줄에서 기다리도록 합니다. 작업이 완료되고 빠져나갈 때는 x.signal() 함수를 통해 기다리고 있는 프로세스가 있으면 그 중 하나를 깨워줍니다.
그럼 지난 포스팅에서 세마포어로 다루었던 문제 상황들을 모니터 방식을 통해 해결하는 방법을 소개드리겠습니다.
2. Bounded-Buffer Problem (생산자 - 소비자 문제)
이전 포스팅에서 세마포어를 활용해 해결한 코드는 아래와 같습니다.
생산자가 버퍼에 데이터를 집어넣기 위해서는 버퍼 전체에다가 lock을 걸어 다른 생산자가 접근하지 못하게 했습니다. 그 후 버퍼에 데이터를 넣고 Lock을 풀어주었습니다.
이를 모니터 방식으로 변환한 코드는 아래와 같습니다.
모니터에서는 생산자이든 소비자이든 들어와서 코드를 실행하는 도중에 다른 프로세스의 접근을 모니터가 막아주기 때문에, 굳이 공유 버퍼에 대해 lock을 걸거나 lock을 푸는 코드가 필요하지 않습니다.
생산자인 경우에 비어있는 버퍼가 없다면, 빈 버퍼를 기다리는 줄에서 Blocked 상태로 대기하게 됩니다. 버퍼가 비어있다면 그냥 버퍼에다가 내용을 집어넣어주면 됩니다. 그리고 그 작업이 끝나면 혹시 내용이 들어있는 버퍼가 없어서 잠들어있는 소비자 프로세스가 있다면 그것을 깨워줍니다. 소비자는 반대의 매커니즘으로 동작합니다.
프로그래머 입장에서는 세마포어보다 더 이해가 쉽고 락을 걸 필요가 없다는 장점이 있습니다. 즉 모니터 안에서는 하나의 프로세스만 활성화되기 때문에 나머지 프로세스는 기다리게 하는 wait 연산이 필요하고, 그러다 프로세스 작업이 끝나면 queue에 줄 서 있는 프로세스 하나를 깨워주는 signal 연산이 필요한 것입니다. 그래서 모니터에서는 condition variable 변수가 값을 가지지 않고 자신의 큐에 프로세스를 매달아서 sleep 시키거나 큐에서 프로세스를 깨우는 역할을 합니다.
3. Dining Philosophers Problem (식사하는 철학자 문제)
각각의 철학자들은 식사하거나 생각하거나의 과정을 반복합니다. 밥을 먹기 위해서는 양쪽의 젓가락을 들어야합니다. 젓가락을 집는 부분에 대해서만 간단히 짚어보겠습니다.
젓가락이라는 공유 자원을 접근하기 위해 모니터로 정의합니다. 젓가락을 잡는 pickup 코드는 모니터 내부의 코드로 되어있습니다. 그래서 모니터 안에서 자원에 접근하는 코드를 만들었기 때문에 Lock을 걸거나 푸는 것이 필요가 없습니다. test()를 통해 양쪽의 젓가락을 모두 집을 수 있는지 체크하고, 만약 상태가 식사하려는 상태라면 wait()을 통해 큐에서 기다리게 합니다.
참고자료
[KOCW 이화여대 반효경 교수님 - Process Synchronization 4]
https://core.ewha.ac.kr/publicview/C0101020140411143154161543?vmode=f
[ Chapter5 Operating System Concepts - Abraham Silberschatz ]
https://www.yes24.com/Product/Goods/89496122