S E P H ' S
[OS] 모니터 본문
세마포는 매우 오래된 동기화 도구다. 현재는 모니터(monitor) 라는 동기화 도구를 주로 사용하며 이는 좀 더 고수준의 동기화 기능을 제공한다.
1. 모니터 구조
위는 모니터 구조를 간단히 나타낸 그림이다. 모니터는 공유자원 + 공유자원 접근함수로 이뤄져 있고, 2개의 큐를 지니고 있다. 각각
Mutual Exclusion Queue(상호배제 큐), Conditional Synchronization Queue(조건동기 큐)이다.
- 상호배제 큐는 말그대로 공유자원에 하나의 프로세스만 진입하도록 하기 위한 큐이다.
- 조건동기 큐는 이미 공유자원을 사용하고 있는 프로세스가 특정한 호출 wait()을 통해 조건동기 큐로 들어갈 수 있다.
조건동기 큐에 들어가있는 프로세스는 공유자원을 사용하고 있는 다른 프로세스에 의해 깨워질 수 있다. 이 역시 깨워주는 프로세스에서 특정한 호출 notify()를 해주며, 깨우더라도 공유자원을 사용하고 있는 프로세스가 해당 구역을 나가야 비로소 큐에 있던 프로세스가 실행된다.
2. 자바 모니터
자바는 모니터를 제공하는 대표적인 언어이며, 자바의 모든 개체는 모니터가 될 수 있다.
class C {
private int value; // 공유 변수
synchronized void Foo() { // 배제 동기
// ...
}
synchronized void Goo() {
// ...
}
void H() {
// ...
}
}
위 클래스는 모니터를 사용하고 있는 클래스이다. value와 같은 변수들은 여러 스레드가 공유하고 있는 변수로 볼 수 있고, synchronized 키워드는 배제동기를 수행하는 함수를 말한다. 즉, 해당 함수에는 단 하나의 스레드만 접근할 수 있다.
Foo() 함수와 Goo() 함수는 synchronized 키워드를 통해 상호배제 함수로 선언했는데 이는 "둘 다 같은 임계구역을 갖는다"는 의미이다. 다시 말해 Foo() 함수에 한 스레드가 수행중이면 Foo() 함수 뿐아니라 Goo() 함수에도 다른 스레드는 접근할 수 없다. 반면에 H() 함수는 일반 함수인데 이 함수에서는 공통 변수에 대한 업데이트를 하지 않는다는 것을 예상할 수 있다. (여러 스레드가 동시에 접근가능하다.)
조건동기는 특정 메소드 호출로 사용할 수 있다.
- wait() : 호출한 스레드를 조건동기 큐에 삽입.
- notify() : 조건동기 큐에 있는 하나의 스레드를 깨워준다.
- notifyAll() : 조건동기 큐에 있는 모든 스레드를 깨워준다.
모니터 역시, 세마포에서 할 수 있는 기능인 Mutual Exclusion, Ordering 을 모두 수행할 수 있다. 예제를 통해 살펴보자.
2.1 BankAccount Problem
이전 세마포에서 살펴본 은행계좌 문제를 통해 세마포 대신 모니터를 사용하여 Mutual Exclustion, Ordering을 구현해보자.
2.1.1 Mutual Exclustion
public class BankAccount_use_Monitor {
static class Test {
public static void main(String[] args) throws InterruptedException {
BankAccount b = new BankAccount();
Parent p = new Parent(b);
Child c = new Child(b);
p.start(); // start() : 쓰레드 실행 메소드
c.start();
p.join(); // join() : 쓰레드가 끝나기를 기다리는 메소드
c.join();
System.out.println("\n balance = " + b.getBalance());
}
}
static class BankAccount {
int balance;
synchronized void deposit(int amt) {
int tmp = balance + amt;
System.out.print("+");
balance = tmp;
}
synchronized void withdraw(int amt) {
int tmp = balance - amt;
System.out.print("-");
balance = tmp;
}
int getBalance() {
return balance;
}
}
static class Parent extends Thread {
BankAccount b;
Parent(BankAccount b) {
this.b = b;
}
public void run() {
for (int i = 0; i < 100; i++) {
b.deposit(1000);
}
}
}
static class Child extends Thread {
BankAccount b;
Child(BankAccount b) {
this.b = b;
}
public void run() {
for (int i = 0; i < 100; i++) {
b.withdraw(1000);
}
}
}
}
세마포를 사용할 때보다 훨씬 더 간결하게 코드를 구현할 수 있다. 세마포를 선언하고 number of permit 값을 설정하는 것 대신 synchronized 키워드 하나로 이를 대체한 것이다.
+++++++++++---++++++++--------------------------------------------
----------------------------------++++-------------------+++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++
balance = 0
실행 결과는 위와 같고 balance 값이 정상적으로 0으로 출력된다.
2.1.1 Ordering
Ordering을 위해 보니터를 어떻게 사용하눈지 확인해보도록 하자.
P1 | P2 |
wait() | |
Section 1 | Section 2 |
notify() |
위 구조는 프로세스 순서를 P1, P2 순서로 실행하기 원하는 경우이다. 이는 세마포와 매우 유사한 구조이다. 그러면 은행계좌 모니터를 구현하는데 입금 -> 출금 순으로 수행하고 이를 반복 수행하는 3가지 구조를 각각 구현해볼 것이다. 위 코드에서 수정하는 부분은 순서를 정하는 입금, 출금함수이다.
- 입금먼저 수행
static class BankAccount {
int balance;
synchronized void deposit(int amt) {
int tmp = balance + amt;
System.out.print("+");
balance = tmp;
notify();
}
synchronized void withdraw(int amt) {
while (balance <= 0) {
try {
wait();
} catch (InterruptedException e) {}
}
int tmp = balance - amt;
System.out.print("-");
balance = tmp;
}
int getBalance() {
return balance;
}
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++------------------------------
-----------------------------------------------------------------
-----
balance = 0
- 출금먼저 수행
static class BankAccount {
int balance;
synchronized void deposit(int amt) {
while (balance == 0) {
try {
wait();
} catch (InterruptedException e) {}
}
int tmp = balance + amt;
System.out.print("+");
balance = tmp;
}
synchronized void withdraw(int amt) {
int tmp = balance - amt;
System.out.print("-");
balance = tmp;
notify();
}
int getBalance() {
return balance;
}
}
----------------------------------------+++++++++++++++++++++
+++++++++++++++++++------------------------------------------
------------------+++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++
balance = 0
- 입출금 반복 수행
static class BankAccount {
int balance;
boolean p_turn = true;
synchronized void deposit(int amt) {
int tmp = balance + amt;
System.out.print("+");
balance = tmp;
notify();
p_turn = false;
try {
wait();
} catch (InterruptedException e) {}
}
synchronized void withdraw(int amt) {
while (p_turn) {
try {
wait();
} catch (InterruptedException e) {}
}
int tmp = balance - amt;
System.out.print("-");
balance = tmp;
notify();
p_turn = true;
}
int getBalance() {
return balance;
}
}
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+-+-+-+-+-+-+-+-+-+-
balance = 0
2.2 전통적 동기화 문제
2.2.1 Producer-Consumer Problem
public class Producer_Consumer_use_Monitor {
static class Test {
public static void main(String[] args) {
Buffer b = new Buffer(100);
Producer p = new Producer(b, 1000);
Consumer c = new Consumer(b, 1000);
p.start();
c.start();
try {
p.join();
c.join();
} catch (InterruptedException e) {}
System.out.println("Number of items in the buf is : " + b.count);
}
}
// 버퍼 : 공유자원
static class Buffer {
int[] buf;
int size, count, in, out;
Buffer(int size) {
buf = new int[size];
this.size = size;
count = in = out = 0;
}
synchronized void insert(int item) {
while (count == size) {
try {
wait();
} catch (InterruptedException e) {}
}
buf[in] = item;
in = (in + 1) % size;
notify();
count++;
}
synchronized int remove() {
while (count == 0) {
try {
wait();
} catch (InterruptedException e) {}
}
int item = buf[out];
out = (out + 1) % size;
count--;
notify();
return item;
}
}
static class Producer extends Thread {
Buffer b;
int N;
Producer(Buffer b, int N) {
this.b = b;
this.N = N;
}
public void run() {
for (int i = 0; i < N; i++) {
b.insert(i);
}
}
}
static class Consumer extends Thread {
Buffer b;
int N;
Consumer(Buffer b, int N) {
this.b = b;
this.N = N;
}
public void run() {
int item;
for (int i = 0; i < N; i++) {
item = b.remove();
}
}
}
}
Number of items in the buf is : 0
'CS > OS' 카테고리의 다른 글
[OS] 페이징 (1) | 2023.11.29 |
---|---|
[OS] 주기억장치 관리 (0) | 2023.11.29 |
[OS] 프로세스 동기화 3 (3) | 2023.11.27 |
[OS] 프로세스 동기화 2 (1) | 2023.11.27 |
[OS] 프로세스 동기화 1 (1) | 2023.11.24 |