우리네 장
[ OS ] 동기화 기법 소개2_모니터! ( monitor! ) 본문
모니터... 그 모니터가 아니었다...!^^
이전에 작성했던 스핀락 + 뮤텍스 + 세마포어 의 개념을 모니터를 이해한 후 그 쓰임에 대해 확실히 알게 되었다!
모니터 ( Monitor )
일단 모니터 또한 동기화 기법 중 하나이다.
기본 동기화 기법을 좀 더 응용한 메카니즘...?
모니터가 중요하게 느껴지는 이유는 Java에서 사용하고 있음일 것이다!
모니터를 이루는 구성 요소는 다음과 같다.
: Mutex ( 뮤텍스 lock ) + Condition Variable
뮤텍스는 전 게시글에서 보았으나,
Condition Variable은 무엇일까??
모니터의 사용 목적을 먼저 보자!
1. 상호 배제 (mutual exclusion)을 보장한다.
2. 조건에 따라 스레드를 대기 상태 ( sleep )로 전환 하는 기능을 제공한다.
2. 이 뭔 말인가 싶은데..
A 기능을 수행 하는데 서로 다른 스레드 들이 A 기능을 위한 역할을 분담하여 맡고 있다.
역할을 수행할 때는 임의의 조건이 맞을 때에만 스레드들이 동작을 해야 하고,
조건이 맞지 않을 때에는 동작을 하지 말아야 한다.
이때 스레드를 대기 상태로 전환하는 것을 말한다.
그런데~~~
굳이 스레드가 대기 상태로 전환까지 하면서, 작업을 이어해야 할 필요가 있을까?? 싶었다.
1. 사용자의 요청에 의한 스레드
2. 잦은 주기로 실행되는 것이 아닌, 특정 주기에 한 번만 실행되는 스레드인 경우.
1번과 2번의 경우에 스레드가 조건에 안 맞는다고 던져버리면, 이 작업을 대신 해줄 수 있는 스레드가 없기 때문에
스레드가 해당 작업을 이어서 해줘야 task가 완료 된다고 생각되었다.
쨋든, 2. 조건에 따라 스레드를 대기 상태 ( sleep )로 전환 하는 기능을 제공한다. 여기에 나오는 "조건"이
Conditon Variable 이다.
살짝 헷갈릴 수 있지만 이 조건은 lock 획득과 관련된 조건이 전혀 아닌, 기능 로직과 관련된 조건이다.
* Conditon Variable : 조건이 충족될 때까지 대기 상태의 스레드들이 머무는 waiting queue를 가짐.
CV의 operation은
waiting : 스레드를 대기 상태로 전환 하는 것
signal : 대기 중인 스레드들 중 하나를 다시 깨우는 것
broadcast : 대기 중인 스레드들 모두 깨우는 것
이 있다.
참고로 이와 비교하여, 뮤텍스에서 lock을 취득하기 위해 스레드들이 대기하고 있는 queue는 " entry queue " 라고 한다.
이 entry queue는 뮤텍스에 속해 있으며, 어떤 큐인지에 따라서 실행되는 스레드들의 순서는 달라진다.
선입 선출인 경우도 있고, queue 안의 요소들을 같은 우선 순위로 보고 경쟁을 시켜 실행하도록 구현된 케이스도 있다.
그래서 어떤 lock을 획득 하는 스레드의 순서를 FIFO라고 장담할 수 없다.
acquire ( ml ); //ml : mutex lock, 사실은 lock도 여러 스레드들이 접근하는 공유자원이므로 spin lock등으로 접근 제어를 받아야 맞다고 생각한다...^^
---------------------------------------------------------------- critical section 시작
while ( !p ) { // * *100! 공유자원의 상태가 임의 스레드가 쉬고 있는 동안 어떻게 바뀔지 모르기 때문에 항상 해당 자원의 조건문 ( p ) 을 whie 문으로 하여 그 안에 wait 절이 있어야 한다.
wait ( ml, cv ); // ml : 스레드가 대기 상태가 되면 가지고 있던 lock을 반환해야 하므로, 매개변수에 ml이 들어간다.
cv : condition variable, 스레드가 대기 상태가 되어 해당 cv이 가진 waiting queue 에 들어가게 되므 로 매개변수에 cv가 들어간다.
}
~~ code 작업 ~~
signal ( cv or cv2 ); - or - broadcast ( cv or cv2 ); // 다른 T/P와 협업 시 해당 역할을 하는 T/P가 대기 상태에서 나와 코
드 를 실행할 수 있도록 상대편 cv2의 waiting queue에 속한 스레드 들을 깨워준다. ( 본인 cv의 것을 깨울수도 있다! )
---------------------------------------------------------------- critical section 끝
release ( ml );
코드로 한 번 살펴보자!
bounded producer / consumer example
producer : buffer 라는 공유 자원에 task를 넣는 역할
consumer : buffer 라는 공유 자원에 task를 빼서 수행하는 역할
global volatile Buffer bf; //공유자원
global Lock lock; //공유자원
global CV fullCV;
global CV emptyCV;
//역할1
public method producer() {
while ( true ) {
Task t = ~ ;
lock.acquire();
------------------------------------------------ 임계 영역 -------------------------------------------------
while ( bf.isFull() ) {
wait( lock, fullCV );
}
bf.enQueue( t );
signal ( emptyCV ) or broadcast ( emptyCV ); // buffer에 task가 들어가면 consumer가 처리를 할 수 있는 상태
이므로, emptyCV의 waiting Queue에 있는 스레드들을 깨워준다.
** 이때 깨어난 스레드들이 바로 lock을 획득하지 못하면, entry queue로 진입하게 된다.
------------------------------------------------ 임계 영역 -------------------------------------------------
lock.release();
}
}
//역할2
public method consumer() {
while ( true ) {
lock.acquire();
------------------------------------------------ 임계 영역 -------------------------------------------------
while( bf.isEmpty() ){
wait ( lock, emptyCV );
}
Task t = bf.getQueue();
signal( fullCV ) or broadCast ( fullCV ); // buffer에 task가 나가면 producer가 task를 넣을 수 있는 상태 이므로,
fullCV의 waiting Queue에 있는 스레드들을 깨워준다.
** 이때 깨어난 스레드들이 바로 lock을 획득하지 못하면, entry queue로 진입하게 된다.
------------------------------------------------ 임계 영역 -------------------------------------------------
lock.release();
~~ task handle code ~~~
}
}
** 이때 깨어난 스레드들이 바로 lock을 획득하지 못하면, entry queue로 진입하게 된다.
를 이해하기 위해 예시를 들어보자!
producer 역할을 하는 스레드를 P1,
consumer 역할을 하는 스레드를 C1 이라고 한다.
1. 처음에 C1이 먼저 lock.acquire() 한다.
2. while에 접근하였으나, buffer가 비어있으므로 스레드가 emptyCV 의 waiting queue에 들어가 대기하게 된다.
- 이때 C1의 lock은 반환된다.
그런데 이때, P1이 producer()를 실행하려고 한다.
1. 마침 lock 점유가 없어 lock.acquie() 한다.
2. buffer가 비어있으므로 while문에 걸리지 않아 대기 상태에 빠지지 않는다.
3. 비즈니스 로직 상, buffer에 task를 넣는다.
4. 이제 buffer에 task가 있기 때문에 consumer()를 실행하기에 적합하므로, emptyCV의 waiting queue에 들어간 스레드들을 깨워준다
- signal or broadcast ( emptyCV )
- 이때, 두 가지 경우가 있다.
1) signal and continue 방식
2) signal and wait 방식
현재 lock을 점유하여 코드를 실행하고 있는 T와 대기에서 깨어난 T를 어떻게 할 것인가? 와 관련된 방법이다.
5. 작업이 끝났으면 lock을 해제한다.
[ signal and continue ]
lock을 가진 thread가 CV가 소유한 waiting queue에서 대기하고 있는 T를 깨우고,
lock을 여전히 본인이 점유하면서 그 다음 코드를 진행,
깨어난 T는 lock을 점유하지 못했으므로 entry queue에서 대기
[ signal and wait ]
lock을 가진 thread가 CV가 소유한 waiting queue에서 대기하고 있는 T를 깨우고,
lock을 깨어난 T에게 위임.
본인은 lock을 잃어서 entry queue로 들어가고, 깨어난 T는 lock을 획득하여 대기 전에 실행했던 코드를 이어서 실행
이어서 실행????????
- thread에 속한 PC에는 해당 thread가 다음에 진행해야 할 코드 주소를 가지고 있다. PC의 도움으로 이어서 실행이 가능하다.
JAVA에서의 Monitor!
멋쟁이 자바...
자바는 Monitor를 지원한다.
Monitor는 Mutex를 지원한다.
자바에서 Mutex를 어떻게 구현했는지는 모르지만, OS가 제공하는 API에 의존했을 것 같다.
일단, 자바에서는 모든 객체가 Monitor를 하나씩 가지고 있다.
이때의 Monitor는 Mutex + Condition Variable 을 의미하므로
1 객체 = 1 Mutex, 1 CV 를 가진다고 생각할 수 있다.
자바에서는 "synchronized" 키워드를 사용해 Mutex를 시용하여 상호 배제를 구현한다.
또한 CV operation의 명칭이 다르다.
: wait / notify / notifyAll 로 사용한다.
위 예시를 자바 코드로 봐보자!
출처 : https://www.youtube.com/watch?v=Dms1oBmRAlo&list=PLcXyemr8ZeoQOtSUjwaer0VMJSMfa-9G-&index=9
'OS' 카테고리의 다른 글
[ 동기화 #3 ] 세마포어 (1) | 2023.12.27 |
---|---|
[ 동기화 #2 ] 뮤텍스 (3) | 2023.12.27 |
[ 동기화 #1 ] 스핀락 (0) | 2023.12.27 |
[ OS ] 동기화 기법 소개3_Thread.join() 분석해보자! (1) | 2022.10.02 |
SSH, Telnet 프로토콜 비교 (0) | 2021.01.21 |