May 25, 2020
참고도서: Operating System Concepts (10/E) Abraham Silberschatz, Peter B. Galvin, Greg Gagne
lock 과 semaphore의 사용은 임계구역 문제를 어느정도 해결해주지만, block 지점을 개발자가 직접 설계해서 구현해야하는 어려움이 있다. 때문에 자칫 잘못하면 세마포어를 잘못 사용해서 잘못된 결과를 만들어낼 수도 있다. 예를 들어 wait() 가 실행되어야 하는 구간에 signal()을 실행시켜버리면 동시에 여러 프로세스가 임계구역에 접근할 수 있게되고, sempahore 의 사용이 의미가 없어진다. 혹은 signal() 을 실행시켜야하는 구간에 wait()를 실행시키게되면 어떤 프로세스도 임계구역에 진입하지 못하고 계속 기다리는 데드락 상태가 되어버린다.
모니터는 세마포어의 문제를 해결하기 위한 Abstract Data Type(ADT) 이다. 개발자는 모니터안에 임계구역에 대한 작업을 정의해둘 수 있고, 각 프로세스나 스레드는 한번에 하나씩만 해당 작업에 접근하여 작업을 수행할 수 있다.
monitor monitor name {
/* shared variable declarations */
function P1 () {
}
funtion P2 () {
}
...
initialization_code () {
}
}모니터는 일반적으로 위와 같은 구조를 지니는데, 모니터 내부에 있는 변수들은 외부에서 접근할 수 없고 모니터 내에 정의된 함수를 통해서만 접근이 가능하다. 따라서 개발자는 세마포어처럼 명시적으로 어떤 구간을 정해서 임계구역 접근을 제어하지 않고, 모니터를 통해서 손쉽게 동기화 문제를 해결할 수 있다는 장점이 있다. 근데 이렇게만 하면 단순히 Mutex lock을 이용해 임계구역 진입을 제어하는 것과 다를 바가 없고, deadlock 문제가 여전히 존재하게된다. 따라서 모니터는 condition variable 을 사용해서 추가적으로 발생할 수 있는 동기화 문제를 방지한다.
조건 변수는 wait과 signal 두 가지 연산을 가진다. 세마포어에서 봤던 것과 비슷한 역할을 한다. 만약 wait() 이 실행되면, 해당 프로세스는 대기하게 되는데, 다른 프로세스가 signal() 을 실행할 때까지 대기하게 된다.
한가지 생각해보아야 할 점은 wait 이 되어 대기중이던 프로세스가 모니터에 들어온 어떤 다른 프로세스에 의해 signal 되어 모니터에서 작업을 재개할 때, 모니터 안에 두 프로세스가 존재할 수 있다는 것이다. 따라서 우리는 두 가지 방법을 생각해볼 수 있다.
Signal-and-Wait : signal 을 보낸 프로세스가 대기하고 다시 시작된 프로세스가 끝나기를 기다린다.Signal-and-Continue : signal 을 보낸 프로세스는 signal 이후에 작업을 계속 진행하고 이 프로세스가 끝나면 signal을 받은 프로세스가 작업을 시작한다.두 방법 중 어떤 방법을 택하는지는 모니터를 사용하는 프로그래밍 언어가 어떻게 설계되었는지에 따라 달라진다. 모니터는 high-level language 에서만 지원되기 때문에, Java에서는 단순 명령어 하나로 모니터를 사용할 수 있다.
그렇다면 deadlock의 발생 위험이 있었던 식사하는 철학자 문제를 모니터를 이용해서 완벽하게 해결해보자. 먼저 이 문제를 해결하기 위해서, 모든 철학자들이 양쪽에 있는 젓가락을 모두 사용 가능해야 젓가락을 집어든다고 가정하자. 그리고 각 철학자들은 멍때리기, 식사 시도하기, 식사하기 세 상태 중 하나를 가져야 하기 때문에 다음과 같이 모니터의 공통 변수를 설정할 수 있다.
enum {THINKING, HUNGRY, EATING} state[5]; // 철학자는 5명이 있다고 가정했다.어떤 철학자의 상태가 EATING 이 되기 위해서는 자신의 양쪽에 있는 철학자들의 상태가 EATING 이면 안된다. 따라서 5] != EATING) && (state[(i + 1)
의 조건이 만족될 때만 젓가락을 집어들 수 있다. 이 조건을 모니터에서 사용하게 되면, 어떤 철학자의 상태가 HUNGRY 이고 양쪽에 집어들 수 있는 젓가락이 없을 때, wait()을 통해서 젓가락이 사용가능할 때까징기다리게 할 수 있다.
그럼 모니터로 구현한 코드를 살펴보자
monitor DiningPhilosophres{
enum {THINKING, HUNGRY, EATING} state[5];
condition self[5];
void pickup(int i){
state[i] = HUNGRY;
test(i);
if (state != EATING)
self[i].wait();
}
void putdown (int i){
state[i] = THINKING;
test((i + 4) % 5);
test((i + 1) % 5);
}
void test (int i){
if ((state[(i + 4) % 5] != EATING)
&& (state[i] == HUNGRY)
&& (state[(i + 1) % 5] != EATING)){
state[i] = EATING;
self[i].signal();
}
}
initialization_code (){
for (int i = 0 ; i < 5 ; i++)
state[i] = THINKING
}
}가장 먼저 공통변수를 확인해보자 앞서 확인했던 것 처럼 세 상태를 표현하기 위한 state 배열이 사용되었다. 그리고 조건변수로 self 배열이 사용되었는데, 배열의 각 요소가 각 철학자들의 상태를 변경시킨다.
initialization_code (){
for (int i = 0 ; i < 5 ; i++)
state[i] = THINKING
}모니터 내부에 정의된 함수는 총 네 개의 함수이다. 우선 initialization_code 함수를 보자. 가장 처음에는 모든 철학자들의 상태를 THINKING으로 초기화 한다. 이 부분에 있어서는 크게 복잡한 것이 없다.
void test (int i){
if ((state[(i + 4) % 5] != EATING)
&& (state[i] == HUNGRY)
&& (state[(i + 1) % 5] != EATING)){
state[i] = EATING;
self[i].signal();
}
}또 다른 함수는 자신의 주변에 있는 철학자들의 상태를 확인하는 함수이다. 조건문이 조금 복잡하게 얽혀있는데 하나씩 쪼개어서 보자.
5] != EATING : 자신의 왼쪽에 있는 철학자가 EATING 상태에 있는지 확인한다.state[i] == HUNGRY : 자기자신이 HUNGRY 상태인지 확인한다. 5] != EATING) : 자신의 오른쪽에 있는 철학자가 EATING 상태에 있는지 확인한다.세 조건을 모두 확인했을 때 왼쪽과 오른쪽에 있는 철학자들이 모두 EATING 상태가 아니고 자기 자신이 HUNGRY 상태라면 식사할 수 있는 조건이 만족되기 때문에 식사를 시작하기 위해 자기 자신의 state를 바꾸고 signal 함수를 실행해서 작업을 시작시킨다.
void pickup(int i){
state[i] = HUNGRY;
test(i);
if (state != EATING)
self[i].wait();
}pickup 함수는 젓가락을 들기위한 함수이다. 먼저 젓가락을 들기위해 자기자신의 state 를 HUNGRY로 변경한다. 그리고 test 를 실행해서 양쪽에 있는 젓가락을 들 수 있는 상태인지 확인한다. 만약 양쪽 젓가락을 모두 들 수 있다면, test 함수에서 해당 철학자의 상태가 EATING 으로 변경되기 때문에 pickup은 성공한채로 함수를 종료시킨다. 하지만 만약 젓가락을 들 수 없는 상태였다면 철학자의 상태가 변경되지 않고 HUNGRY로 남아있기 때문에 자기 자신에게 wait 을 실행해서 signal 을 기다리며 대기하는 상태가 된다.
void putdown (int i){
state[i] = THINKING;
test((i + 4) % 5);
test((i + 1) % 5);
}putdown 함수는 식사를 마치고 젓가락을 내려놓게 하는 함수이다. 이 함수가 실행되면, 해당 철학자의 상태는 THINKING 으로 변경되고, 왼쪽과 오른쪽 사람에 대해 각각 test 함수를 실행하게 된다. 이미 자기자신은 THINKING 상태가 되어 EATING 상태가 아니기 때문에, 만약 왼쪽이나 오른쪽에 signal을 기다리며 대기중이던 철학자가 있다면 test 를 통해 해당 철학자를 깨울 수 있게된다. 이 작업을 통해서 어떤 철학자가 식사를 마쳤을 때, 양 옆에 있는 철학자의 대기상태를 확인하게 되기 때문에, deadlock 문제를 해결할 수 있게 된다.