
⚠️ 해당 포스팅은 인프런 공룡책 강의를 듣고 개인적으로 정리하는 글입니다. 정확하지 않은 정보가 있을 수 있으니 주의를 요합니다.
Chapter 7 CPU Synchronization Example

mutex가 있다.mutex는 버퍼 풀에 접근할 때 상호 배제를 제공하는 역할을 맡게된다.empty, full이 있다.empty는 초기값을 n으로, full은 초기값을 0으로 배정한다.while(true) {
	/* produce an item in next_produced */
    . . .
    wait(empty); // buffer
    wait(mutex); // mutex
    
    /* add next_produced to the buffer */
    
    signal(mutex);
    signal(full);
}
while(true) {
	/* remove an item from buffer to next_consumed */
    . . .
    wait(full); // 다 찰 때까지 대기한다.
    wait(mutex); 
    
    /* consume the itenm in next_consumed */
    
    signal(mutex);
    signal(empty); // 버퍼가 비었음을 알린다.
}
위의 방법에서 생산자는 아래와 같이 동작한다.
: 소비자가 버퍼를 전부 소모할 때까지 대기한다.
: 다 비우면 생산자가 동작한다.
: 생산자 여러 개가 움직이게 될 수도 있기 때문에 mutex로 상호 배제한다.
자바의 코드도 아래 첨부한다.
public class BoundedBuffer {
    public static void main(String[] args) {
        CashBox cashBox = new CashBox(5);
        Thread[] producers = new Thread[1];
        Thread[] consumers = new Thread[1];
        for (int i = 0; i<producers.length; i++) {
            producers[i] = new Thread(new ProdRunner(cashBox));
            producers[i].start();
        }
        for (int i = 0; i < consumers.length; i ++) {
            consumers[i] = new Thread(new ConsRunner(cashBox));
            consumers[i].start();
        }
    }
}
class ProdRunner implements Runnable{
    CashBox cashBox;
    public ProdRunner (CashBox cashBox) {
        this.cashBox = cashBox;
    }
    @Override
    public void run() {
        try {
            while(true) {
                Thread.sleep((long)(Math.random()*500));
                int money = ((int)(1 + Math.random()*9))*10000;
                cashBox.give(money);
            }
        }catch(InterruptedException e) {}
    }
}
class ConsRunner implements Runnable{
    CashBox cashBox;
    public ConsRunner (CashBox cashBox) {
        this.cashBox = cashBox;
    }
    @Override
    public void run() {
        try {
            while(true) {
                Thread.sleep((long)(Math.random()*500));
                int money = cashBox.take();
            }
        }catch(InterruptedException e) {}
    }
}
class CashBox{
    private int[] buffer;
    private int count, in, out;
    public CashBox(int bufferSize) {
        buffer = new int[bufferSize];
        count = in = out = 0;
    }
    synchronized public void give(int money) throws InterruptedException{
        while(count == buffer.length) {
            try {
                wait();
            }
            catch(InterruptedException e) {}
        }
        buffer[in] = money;
        in = (in + 1) % buffer.length;
        count++;
        System.out.println("여기있다, 용돈 : " + money + "원");
        notify();
    }
    synchronized public int take() throws InterruptedException{
        while(count == 0) {
            try {
                wait();
            }
            catch(InterruptedException e) {}
        }
        int money = buffer[out];
        out = (out + 1) % buffer.length;
        count --;
        System.out.println("고마워유, 용돈 : " + money + "원");
        notify();
        return money;
    }
}

(각각 동시성을 가지고 돌아가는 프로세스 간에 공유되는 데이터베이스를 생각해보자.)일반적으로는 이 문제에 대해 reader-writer locks로 해결할 수 있다.
: 실행하려는 프로세스가 독자 프로세스인지, 작가 프로세스인지에 따라 락을 다르게 적용한다.
: 독자 프로세스가 락을 획득하면 모든 독자 프로세스에 관한 락은 풀고, 작가 프로세스에만 락을 적용한다.
: 작가 프로세스가 락을 획득하면 작가, 독자 모두에게 락을 적용한다.
결국 뮤텍스락에서 달라진건 독자 프로세스가 작업할 때도 다른 독자 프로세스 진입을 허용하는 것뿐이다.
독자-작가 문제에 대한 몇 가지 변형 예제를 들어보자.
모든 변형에는 우선 순위가 포함된다.
The first readers-writers problem
: 독자 프로세스는 자신 이외의 독자 프로세스가 종료되기 전까지 wait() 을 호출할 수 없다.
: 이 말은, 간단하게 얘기해서 작가 프로세스는 독자 프로세스 모두가 종료될 때까지 대기함을 의미한다.
: 만약 독자 프로세스가 계속해서 물밀듯이 들어오면 작가 프로세스는 기아(Starvation) 상태에 빠질 수 있다.
The second readers-writers problem
: 만약 작가 프로세스가 오브젝트에 접근하면, 새로운 독자 프로세스는 실행할 수 없다.
: 작가 프로세스에게 우선 순위를 높게 주어 먼저 실행하게 만드는 것이다.
: 이 역시, 작가 프로세스가 물밀듯이 들어올 경우 독자 프로세스가 기아 상태에 빠질 수 있다.

rw_mutx 는 독자, 작가 사이의 상호 배제를 위해 사용한다.mutex는 공유 변수인 read_count에 대한 상호 배제를 위해 사용한다.read_count는 얼마나 많은 프로세스가 읽고 있는지 나타내기 위해 사용한다.read_count = 0 -> 작가 프로세스 진입while(true) {
    wait(rw_mutex); // 독자로부터 신호가 오면 진입
    
    /* writing is performed */
    
    signal(rw_mutex);
}
while(true) {
    wait(mutex); // 공유 자원 접근을 위한 요청
	read_count++; // 읽고 있는 프로세스의 수 더하기
    if(read_count == 1)
    	wait(rw_mutex); // 독자 프로세스는 여러 프로세스가 진입이 가능하다.
    signal(mutex);
    
    /* reading is performed */
    
    wait(mutex); // 공유 자원 접근을 위한 요청
    read_count --;
    if (read_count == 0)
    	signal(rw_mutex);    
    signal(mutex); 
}
위의 코드를 보면 어째서 작가 프로세스가 기아 상태에 빠질 수 있는지 파악할 수 있을 것이다.
다시 정리해보자.
: 작가 프로세스가 임계 영역에 접근하면 많은 독자 프로세스는 waiting 상태에 빠질 것이다.
: 이 때 한 개의 독자 프로세스는 rw_mutex에서, 그리고 나머지 독자 프로세스는 mutex에서 대기하고 있을 것이다.
: 또한, 작가가 signal(rw_mutex) 연산을 실행할 때 대기중인 작가 프로세스를 실행할지, 독자 프로세스를 실행할지는 모두 스케줄러에 의해 결정된다.
: 어떤 프로세스의 우선 순위를 높이느냐에 따라 해결책이 갈리게 된다.
: 독자 프로세스의 우선 순위를 높인다면 첫번째 방법, 작가 프로세스의 우선 순위를 높인다면 두번째 방법이 될 것이다.
: 이에 대해 뭐가 좋다는 이야기 할 수 없고, 자신의 환경에 맞춰 설정해야 한다.

while (true) {
	wait (chopstick[i]); // Left
    wait (chopstick[(i+1) % 5]); // Right
    
    /* eat for a while */
    
	signal (chopstick[i]);
    signal (chopstick[(i+1) % 5]);
    
    /* think for a while */
}
wait() 연산을 실행하고, 젓가락을 놓을 때 signal() 연산을 실행한다.다들 왼쪽 젓가락을 잡고 오른쪽 젓가락을 달라고 서로 싸우는 꼴이 된다.철학자, 즉 프로세스의 수를 제한할 수 있을 때 사용한다.thinking, hungry, eating 로 구분한다.eating 상태가 아니어야 한다.hungry 상태이지만 음식을 먹을 수 없을 때 스스로를 지연시키도록 한다.
package Synchronization;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
enum State {
    THINKING, HUNGRY, EATING
}
public class DiningPhilosophers {
    public static void main(String[] args) throws Exception {
        int numOfPhils = 5;
        Philosopher[] philosophers = new Philosopher[numOfPhils];
        DiningPhilosopherMonitor monitor = new DiningPhilosopherMonitor(numOfPhils);
        for (int i = 0; i < philosophers.length; i++)
            new Thread(new Philosopher(i, monitor)).start();
    }
}
class Philosopher implements Runnable {
    private int id;
    private DiningPhilosopherMonitor monitor;
    public Philosopher(int id, DiningPhilosopherMonitor monitor) {
        this.id = id;
        this.monitor = monitor;
    }
    @Override
    public void run() {
        while (true) {
            think();
            monitor.pickup(id);
            eat();
            monitor.putdown(id);
        }
    }
    private void think() {
        try {
            System.out.println(id + ": Now I'm thinking.");
            Thread.sleep((long) (Math.random() * 500));
        }
        catch (InterruptedException e) {
        }
    }
    private void eat() {
        try {
            System.out.println(id + ": Now I'm eating.");
            Thread.sleep((long) (Math.random() * 50));
        }
        catch (InterruptedException e) {
        }
    }
}
class DiningPhilosopherMonitor {
    private int numOfPhils;
    private State[] state;
    private Condition[] self; // Condition Value
    private Lock lock;
    public DiningPhilosopherMonitor(int num) {
        numOfPhils = num;
        state = new State[num];
        self = new Condition[num];
        lock = new ReentrantLock();
        for (int i = 0; i < num; i++) {
            state[i] = State.THINKING;
            self[i] = lock.newCondition(); // Condition
        }
    }
    private int leftOf(int i) {
        return (i + numOfPhils - 1) % numOfPhils;
    }
    private int rightOf(int i) {
        return (i + 1) % numOfPhils;
    }
    private void test(int i) {
        if (state[i] == State.HUNGRY && state[leftOf(i)] != State.EATING && state[rightOf(i)] != State.EATING) {
            state[i] = State.EATING;
            self[i].signal();
        }
    }
    public void pickup(int id) {
        lock.lock();
        try {
            state[id] = State.HUNGRY;
            test(id);
            if (state[id] != State.EATING) self[id].await();
        } catch (InterruptedException e) {
        } finally {
            lock.unlock();
        }
    }
    public void putdown(int id) {
        lock.lock();
        try {
            state[id] = State.THINKING;
            test(leftOf(id));
            test(rightOf(id));
        } finally {
            lock.unlock();
        }
    }
}

DiningPhilosopher 를 사용한다.pickup() 연산을 실행한다.putdown() 연산을 실행한다.