Process synchronization: 모니터(monitor)
세마포어는 동시성 문제를 해결하는데 유용하지만 프로그래머가 올바르게 사용하지 않으면 타이밍 문제
가 발생할 수 있습니다.
타이밍 문제란 타이밍에 따라 문제가 발생할 수도 발생하지 않을 수도 있는 것을 의미하는데 이러한 문제는 대처하기 까다롭고 디버깅에 어려움을 줍니다.
이러한 타이밍 문제를 해결하기 위해 모니터라는 기법이 개발되었습니다.
모니터는 프로세스 간의 상호 배제를 달성하기 위해 프로그래밍 언어로 지원됩니다.
Java 및 C # 등은 해당 언어로 내장된 모니터를 제공합니다.
- Java - Synchronized 키워드
- C # - lock 키워드
세마포어는 프로그래머가 signal과 wait 함수를 사용해서 동기화를 구현해야 하는 반면 모니터는 내부에서 동기화를 관리해주기 때문에 사용방법이 간단합니다.
구조
모니터는 객체 지향 언어(C++, Java)의 클래스와 같은 추상 데이터 타입(abstract data type: ADT)입니다.
모니터 구성
- 공유 데이터
- 공유 데이터를 다루는 연산들 (= procedure)
- 초기화 코드
monitor monitor_name
{
// shared variable declarations
procedure P1 (...) {
...
}
procedure P2 (...) {
...
}
...
procedure Pn (...) {
...
}
initialization code (...) {
...
}
}
특징
- 한 번에 하나의 프로세스만 모니터 개체 내에 프로시저를 사용할 수 있습니다. 모니터에 접근하지 못한 프로세스는 entry queue에서 대기하게 됩니다. (상호배제)
- 모니터 개체 내 공유 자원은 직접 접근할 수 없으며 프로시저를 통해서만 가능합니다. (정보은닉)
- 프로그래머는 이 동기화를 코딩 할 필요가 없습니다.
조건 변수
기본적인 모니터 구조에서는 고전적인 동기화 문제(식사하는 철학자 문제 등)를 해결하는데 한계가 있습니다.
이를 해결하기 위해서 조건변수(condition value)가 추가되었습니다.
조건 변수에는 wait, signal 두 가지 연산이 있습니다.
예를 들어 X라는 조건 변수가 정의되어 있다면 X.wait(), X.signal()로 호출하게 됩니다.
- wait() - 이 연산을 호출한 프로세스는 다른 프로세스가 signal()함수를 호출 할 때까지 차단되고 차단된 프로세스는 해당 조건과 관련된 조건 queue에서 대기하게 됩니다.
- signal() - 대기하고 있던 프로세스를 깨웁니다. 대기중인 프로세스가 없으면 아무 작업도 수행하지 않습니다.
이 구조는 모니터 내의 프로세스가 signal 함수를 호출하여 다른 프로세스를 깨우면 모니터 내에 두 개의 프로세스가 동시에 실행되게 되는 문제가 있습니다. 이를 해결하기 위한 두 가지 해결책이 있습니다.
signal and wait
- 프로세스 P가 signal 함수를 호출하면 프로세스 P는 Q가 모니터를 떠날 때까지 대기를 합니다.
signal and continue
- P가 signal 함수를 호출하면 Q는 P가 모니터를 떠날 때까지 대기를 합니다.
모니터를 사용한 식사하는 철학자 문제 해결
모니터를 이용하여 식사하는 철학자 문제를 해결해 보겠습니다.
이 해결책은 철학자가 젓가락을 둘 다 사용할 수 있는 경우에만 젓가락을 집을 수 있다는 제한을 사용합니다. (데드락 방지를 위해서)
문제를 해결하기 위해 다음 데이터 구조를 사용합니다.
- enum {THINKING, HUNGRY, EATING} state [5];
- 철학자는 3가지 상태를 가집니다.
- THINKING : 철학자가 젓가락을 집기를 원하지 않을 때(임계구역에 접근하고 싶지 않을 때)
- HUNGRY : 철학자가 젓가락을 집기를 원할 때 (임계구역에 접근하고 싶을 때)
- EATING
- 철학자가 두 젓가락을 모두 얻었을 때 (임계구역에 접근 했을 떄)
- 철학자는 두 이웃 철학자가 모두 EATING 상태가 아니고 자신이 HUNGRY 상태일 때만 자신의 상태를 EATING으로 바꿀 수 있습니다.
if (state[(i+4) % 5] != EATING && state[(i+1) % 5] != EATING && state[i] == HUNGRY) { state[i] = EATING; }
- 철학자는 3가지 상태를 가집니다.
- condition self [5];
- 이 조건 변수는 철학자 i가 HUNGRY상태지만 젓가락을 얻을 수 없을 때 대기할 수 있게 합니다.
구현
monitor DiningPhilosophers
{
enum {THINKING, HUNGRY, EATING} state[5]; // 철학자들의 세가지 상태
condition self[5]; // 다섯 철학자 중 i번째 철학자가 젓가락 두 개를 모두 잡을 수 없으면 대기함.
void pickup(int i) { // 철학자는 식사전 젓가락을 집음.
state[i] = HUNGRY;
test(i);
if (state[i] != 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)) { // 두 이웃 철학자가 식사중이 아니고 자신이 HUNGRY 상태일때
state[i] = EATING; // 자신의 상태를 EATING으로 바꾸고
self[i].signal(); // 자기자신을 깨움.
}
}
initialization_code() { // 모든 철학자를 THINKING 상태로 초기화함.
for (int i = 0; i < 5; i++)
state[i] = THINKING;
}
}
철학자는 구현된 DiningPhilosophers 모니터를 공유하고 다음 작업 순서를 사용하여 식사를 합니다.
DiningPhilosophers.pickup(i);
...
eat
...
DiningPhilosophers.putdown(i);
이렇게 모니터를 사용하여 간단하게 동기화를 구현할 수 있습니다.