뮤텍스와 세마포어의 문제
뮤텍스와 세마포어 편하지만 timing error가 발생한다.
특정한 시퀀스를 잘못쓰면 항상일어나지도 않고 잡기도 어려운 문제들이 발생한다.
wait signal 순서 안지키면 critical section에 들어가는 경우 발생
세마포어 문제:
signal(mutex) -> wait(mutex) 순서로 호출한 경우
wait(mutex) -> wait(mutex) 또는 signal(mutex) -> signal(mutex) 순서로 호출한 경우
wait(mutex) 또는 signal(mutex) 호출을 한개 또는 둘다 생략하는 경우
-> 코드가 복잡해지다보면 자주 발생함
=> 좀더 하이어레벨인 monitor을 쓴다.
monitor type
ADT mutual exclusion(상호배제) 를 제공해주는 하나의 data type (클래스)
어떤 variable 선언하고 instance 호출
개발과정에서 동기화를 위해 프로그래머들이 세마포어(또는 뮤텍스 락)을 정확하지 않게 사용할 가능성이 높기때문에 사용
세마포어를 실제로 구현한 프로그램
여러개의 프로세스들이 접근하는 공유자원을 할당하는데 사용됨
데이터 및 프로시저를 포함하는 병행성 구조
병행성 구조: 하나의 프로세스가 독점하는 것이 아닌 여러개의 프로세스들이 병행적으로 처리할 수 있도록 하는 구조
공유 자원을 사용하고 싶은 프로세스는 모니터 내부의 프로시저 함수를 통하여 접근할 수 있다.
한 프로세스가 모니터 내부의 프로시저 함수를 호출하여 공유 자원에 접근하는 동안 다른 프로세스들은 프로시저 함수를 호출할 수 없고 공유 자원에 접근이 불가능하여 대기큐에서 들어가 있는다.
모니터 내의 정의된 프로시저만이 공유 자원(지역 데이터)에 접근할 수 있다.
모니터는 항상 모니터 안에 하나의 프로세스만이 활성화되도록 하여 상호 배제 조건을 만족한다.
프로그래머들은 동기화 제약 조건을 세마포어처럼 명시적으로 코딩해야 할 필요가 없다.
monitor 변수 선언해주고
그 안에 function 동기화된 함수
initialization_code : 초기화 코드

모니터 구조
shared data가 있고 밑에 초기화 코드가 있음
그안에 동기화 된 operation들이 있다.
struct , class 같은 느낌
이 모니터에 defined된 자료구조를 사용하는 스레드들 큐

Conditional Variable
추가해줘야 함
프로세스들의 실행 순서를 보장하기 위해서 조건 변수(Condition Variable)을 사용한다.
니터 내부에서 수행되는 한 프로세스는 공유 자원을 사용하여 수행하다가 특정한 조건(x, y 등)에 걸리게 되면 사용을 멈추고 해당 조건의 대기 큐로 이동하여 대기한다.
x 라는 컨디션변수 wait 호출하고 x라는 컨디션 변수에 signal 호출하고 함

x y 따로 컨디션을 주기위해 추가됨

Java Monitor
완전한 monitor는 아니지만 제공한다.
monitor-lock( intrinsic-lock) 사용한다.
스레드 동기화를 위한 concurrency 메커니즘
synchronized 키워드, wait() , notify()
synchronized keyword
임계영역에 해당하는 코드 블록을 선언할 때 사용하는 자바 키워드
해당 코드 블록(임계영역)에는 모니터락을 획득해야 진입 가능
모니터락을 가진 객체 인스턴스를 지정할 수 있음
메소드에 선언하면 메소드 코드 블록 전체가 임계영역으로 지정됨
이 때, 모니터락을 가진 객체 인스턴스는 this 객체 인스턴스임
synchronized (object) {
// critical section
}
// 메소드
public synchronized void add() {
// critical section
}
synchronized 선언해주면 lock 획득, lock 풀어주고, enry 섹션 , exit 섹션 java가 다 해결해줌
wait() and notify() methods
순서를 정해주기 위해 필요, P() V()에 해당됨
java.lang.Object 클래스에 선언됨: 모든 자바 객체가 가진 메소드임
쓰레드가 어떤 객체의 wait() 메소드를 호출하면 해당 객체의 모니터락을 획득하기 위해 대기 상태(wait 큐)로 진입함.
쓰레드가 어떤 객체의 notify() 메소드를 호출하면 해당 객체 모니터에 대기중인 쓰레드 하나를 깨움.
notify() 대신에 notityAll() 메소드를 호출하면 해당 객체 모니터에 대기중인 쓰레드 전부를 깨움.
public class SynchExample1 {
static class Counter {
public static int count = 0; //static counter 선언
public static void increment() {
count++; //count increment 하는 메소드
}
}
static class MyRunnable implements Runnable {
@Override
public void run() {
for(int i = 0; i < 10000; i++)
Counter.increment(); // count increment 메소드 호출
}
}
public static void main(String[] args) throws Exception {
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new MyRunnable());
threads[i].start();
}
for (int i = 0; i < threads.length; i++)
threads[i].join();
System.out.println("counter = " + Counter.count);
}
}
-> 예상: 10000씩 5번 해줬으니까 50000
결과: 12900 13400.....
public class SynchExample2 {
static class Counter {
public static int count = 0;
synchronized public static void increment() { // sinchronized 선언
count++; // 이부분 이제 critical section
}
}
static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; i++)
Counter.increment();
}
}
-> synchronize 완료 50000 출력
synchronized(object) 이 부분만 synchronized 하겠다.
어떤 오브젝트 인스턴스에 대해 모니터락 획득하겠다.
static이어서그렇지 static아니면 this 써줘서 자기자신 인스턴스 획득할 수 있다.
public class SynchExample3 {
static class Counter {
private static Object object = new Object();
public static int count = 0;
public static void increment() { // 메소드에 해줘도 되지만
synchronized(object) { // 이부분만 하겠다.
count++; // 어떤 오브젝트 인스턴스에 대해 모니터락 획득하겠다.
// static이어서그렇지 static아니면 this
// 자기자신 인스턴스 획득할 수 있다.
}
}
}
static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; i++)
Counter.increment();
}
}
-> synchronized 완료
this 사용, 자기 객체인스턴의 모니터락을 획득해서 얘를 증가시켜라
count 는 static임 5개의 counter 인스턴스가 공유한다.
public class SynchExample4 {
static class Counter {
public static int count = 0;
public void increment() {
synchronized (this) { // this 사용
Counter.count++;
}
}
}
static class MyRunnable implements Runnable {
Counter counter; // static 아니기때문에 따로선언
public MyRunnable(Counter counter) {
this.counter = counter; // 생성자로 객체생성 초기화
}
@Override
public void run() {
for (int i = 0; i < 10000; i++)
counter.increment(); // 호출
}
}
public static void main(String[] args) throws Exception {
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new MyRunnable(new Counter())); //new
threads[i].start();
}
for (int i = 0; i < threads.length; i++)
threads[i].join();
System.out.println("counter = " + Counter.count);
}
}
-> synchronized 실패
this는 자기참조 객체 변수 5개의 인스턴스 각각 자기 this를 가리킴
-> 모니터가 각각 따로 있는 것
카운터 인스턴스 1개만 사용한다. 그걸 파라미터로 넘겨준다.
=> 스레드가 각각 카운터인스턴스를 갖는게 아니라 같은 카운터 객체를 가진다.
스레드는 5개인데 열쇠는 1개인 경우
public static void main(String[] args) throws Exception {
Thread[] threads = new Thread[5];
Counter counter = new Counter(); // 카운터 인스턴스 1개만 사용 new
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new MyRunnable(counter)); // 파라미터로 넘겨줌
threads[i].start();
}
for (int i = 0; i < threads.length; i++)
threads[i].join();
System.out.println("counter = " + Counter.count);
}
Liveness
뮤텍스 , semaphore, monitor 모두 상호배제만 해결해주지 progress (데드락 문제) , bounded waiting (기아 문제)를 해결해주지못한다.
이 문제도 해결하자 -> Liveness
liveness 실패를 일으킬 수 있는 두가지 상황: deadlock, priority inversion
Deadlock
두개의 프로세스가 서로 lock(또는 세마포어)을 원하게 되어 진행되지 않는 상태
2개 이상의 프로세스가 영원히 기다린다.
waiting queue에 있는 프로세스만 포즈시킬 수 있는 이벤트 기다린다.
P0는 P1을 기다리고 P1은 P0을 기다린다. => Deadlock
P1은 열쇠A를 들고 있고 P2는 열쇠 B를 들고있다.
P1이 B를 요청하는데 P2가 가지고 있으므로 block 되버리고 , P2가 A를 요청하면 P1이 block 중이기 lock을 반납하지 못한다.
P0는 세마포어 S와 Q 순서로 얻어야만 임계 영역에 진입할 수 있다.
P1는 세마포어 Q와 S 순서로 얻어야만 임계 영역에 진입할 수 있다.
두개 이상의 세마포어를 가져야 임계 영역에 도달하는 경우에 어떤 한 프로세스가 A라는 세마포어를 가지고 B라는 세마포어를 가지기 전에 A 세마포어를 원하는 다른 프로세스가 B 세마포어를 가지게 된다 -> 둘 다 실행이안됨 => 교착상태가 발생

Priority Inversion(우선순위 역전)
높은 우선순위가 있는 프로세스가 낮은 우선순위가 있는 프로세스한테 밀리는 현상
어떤 kernel 데이터의 read or write 하려고 하는데 이 kernel 데이터는 synchronized 잘 돼있다. 근데 lower priority가 그걸 access 하고 있다.
(1)높은우선순위 P 낮은 우선순위 Q가 있다. Q가 세마포어를 획득해서 실행중이다. 그때 P가 세마포어 요청을하면 Q가 세마포어를 가지고 있기 때문에 P는 대기해야한다.
(2)
우선순위 상,중,하가 있다. 하가 실행중에 키를 가지고 있고 상이 요청을 했지만 하가 키를 반납하지 않아 상이 대기중이다. 그러다가 중이 또 요청을해서 중이 계속 실행되면 하도 중이 끝나길 기다리고, 하는 키반납이 늦어진다.
아빠가 티비보려고 막내한테 나가라했는데 '리모콘' 들고 나감. 엄마가 아빠를 쫓아내고 티비를 보려하지만 아빠가 리모콘을 든 막내를 쫓아냄, 엄마는 기다려야함
priority-inhenritance(우선순위 상속)
그래서 하의 우선순위를 상 우선순위로 높여줘서 작업이 빨리 끝나게 한다.