6-1강, Process Synchronization 1
데이터의 접근
- 데이터가 저장된 위치
- 데이터를 읽어옴
- 연산을 진행
- 연산한 결과를 다시 원래 위치로 반영
- 읽기만 하면 상관이 없으나, 수정할 경우에는 누가 먼저 읽어갔느냐에 따라 결과가 달라질 수 있는 등 문제가 생길 수 있다.
Race Condition
OS에서 race condition은 언제 발생하는가?
- kernel 수행 중에 인터럽트 발생 시
- 프로세스가 시스템 콜을 하여 커널 모드로 수행 중인데, 컨텍스트 스위칭이 일어나는 경우
- 멀티프로세서에서 공유된 메모리 내의 커널 데이터
OS에서의 race condition(1/3)
OS에서의 race condition(2/3)
OS에서의 race condition(3/3)
Process Synchronizaion 문제
- 공유 데이터의 동시 접근은 데이터 불일치 문제를 발생시킨다.
- 일관성(Consistency) 유지를 위해 협력 프로세스 간의 실행 순서를 정해주는 메커니즘이 필요하다.
- race condition을 막기 위해 concurrent process는 동기화(락을 걸어서 하나의 프로세스가 제대로 작업을 하고, 락을 풀어 나머지 프로세스가 제대로 작업을 해 일관성을 유지할 수 있도록)되어야 한다.
critical-section problem
- 코드가 critical section이다. P1 코드가 shared data를 바탕으로 진행 중이면 P2 코드는 기다려야 한다. P1 코드 작업이 끝나면 P2 코드가 들어간다.
* race condition, 경쟁 조건 : 여러 프로세스 혹은 쓰레드가 거의 동시에 같은 데이터를 조작할 때, 타이밍이나 접근 순서에 따라 데이터의 결과가 달라질 수 있는 상황이다.
* synchronization, 동기화 : 여러 프로세스 혹은 쓰레드가 거의 동시에 공유 데이터를 조작하기 위해 실행되어도, 공유 데이터의 일관성을 유지하는 것이다.(즉 기댓값에 맞게 데이터 결과가 나오도록 하는 것)
* critical section, 임계 영역 : 공유 데이터의 일관성을 보장하기 위해, 하나의 프로세스 혹은 쓰레드만 진입해서 실행할 수 있도록 하는 영역이다.
* critical section problem : critical section과 관련된 문제
* critical section problem의 해결책이 되기 위한 조건
-
mutual exclusion, 상호 배제 : 한번에 하나의 프로세스 혹은 쓰레드만 임계 영역에서 실행 가능하다.(임계 영역으로 들어갈 수 있다)
-
progress, 진행 : 임계 영역에 들어간 프로세스 혹은 쓰레드가 없을 때, 프로세스들 혹은 쓰레드들이 임계 영역으로 들어가려고 할 때 그 중 하나는 들어갈 수 있게 해야 한다. 그냥 진행이 계속 될 수 있게 해야 한다고 이해하자.
-
bounded waiting, 한정된 대기 : 어떤 프로세스나 쓰레드가 임계 영역에 못 들어가고 무한하게 대기하고 있으면 안된다는 조건이다.
-
크리티컬 섹션에 들어가기 위한 뼈대
6-1강, Process Synchronization 1 - (2)
- Process가 동시에 실행하면서 문제가 생겨나는 걸 해결하는 것이 Process Synchronization(프로세스 동기화)
- Concurrency Control(병행 제어)과 같다.
Initial Attempts to Solve Problem
- 프로세스들의 일반적인 구조
- 두 개의 프로세스가 있다고 가정(P0, P1)
- entry section에서 critical section(코드)에 들어가기 전 검사(만족 시 락을 건다)
- exit section에서 락을 푼다.
프로그램적 해결법의 충족 조건
- Mutual Exclusion(상호 배제)
- 프로세스 Pi가 critical section 부분을 수행 중이면, 다른 모든 프로세스들은 그 critical section에 들어가면 안된다.
- entry, exit section를 만족시킬 조건
- Progress(진행)
- 어떤 프로세스도 critical section에 있지 않은 상태에서 critical section에 들어가고자 하는 프로세스가 있다면, critical section에 들어가게 해주어야 한다.
- 코드를 잘못 짜면, critical section에 어떤 프로세스도 없음에도 불구하고 두 프로세스가 동시에 들어가고자 할 때 못 들어갈 수도 있다.
- Bounded Waiting(유한 대기)
- 프로세스가 critical에 들어가려고 요청한 후부터, 그 요청이 허용될 때까지 다른 프로세스들이 critical section에 들어가는 횟수에 한계가 있어야 한다.
- 프로세스 입장에서, critical section에 지나치게 못 들어가는 starvation 현상을 막아야 한다.
- 두 프로세스가 번갈아 돌아가고, 다른 하나의 프로세스가 계속 못 들어가는 등
critical section의 문제를 푸는, 즉 락을 걸고 푸는 알고리즘들
- CPU에서 한 인스트럭션이 끝나면, CPU를 인터럽트나 트랩에 의해 빼앗길 수 있다.
- C와 같은 고급언어의 문장들은 단일 인스트럭션이 아니라 여러 인스트럭션이 섞여 있어 CPU를 빼앗길 수 있다.
Algorithm 1
- critical section에 들어갈 차례가 누구인가를 나타내는 것이 turn 변수.
- turn이 1이면 1번 프로세스가 들어갈 차례이다.
- process 0의 입장에서, turn이 1이라면, while 문에서 계속 기다리게 된다. 그러다가 1번이 끝나고 turn이 0으로 바뀌면 process가 critical section에 들어간다
- 즉 자신의 turn인 0이 아니라면 while문에서 계속 기다린다.
- 들어갔다나오면 상대방 차례로 만들기 위해 turn을 1로 바꾼다.
- 위 코드는 Mutual Exclusion을 만족한다. 하지만 Progress 조건은 만족하지 않는다.
- 프로세스가 반드시 교대로 들어가도록 되어 있다.
- 둘이 critical section으로 들어가려는 빈도가 다를 수 있다.
- P0이 계속 들어가고 싶어도 P1이 들어갔다가 나와야 들어갈 수 있다.
- 상대방이 turn을 바꿔줘야, 항상 다음 프로세스가 들어갈 수 있다.
Algorithm 2
- flag라는 변수를 사용 중이다.
- 프로세스마다 flag를 가지고 있다.
- 본인이 크리티컬 섹션으로 들어가고자 하는 의도를 표시하는 것이다.
- false이면 둘 다 들어가려고 하는 상황이 아니라는 것이다.
- 들어가고자 하면 true로 바꾼다.
- 들어가려고 할 때 상대방의 flag를 확인한다. 만약 상대방이 true로, 크리티컬 영역에 들어가 있다면 while 문에서 계속 기다린다.
- Pi가 flag = true로, do를 실행하다가, flag[i] = true로 Pj가 들어간다는 인터럽트가 발생해 Pj도 do를 실행하게 되면 Pj도 flag = true가 된다. 그러면 둘다 true가 된다.
- 지금 Pj가 CPU를 가지고 있는 상태에서 Pi가 true이기 때문에 Pj는 critical section으로 들어가지 못한다.
- Pj의 CPU 할당시간이 끝나고, Pi가 CPU를 가지고 있을 때, 이때 Pj도 true이기 때문에 critical section으로 들어가지 못한다.
- 따라서 둘 다 못 들어가는, Progress 조건을 만족시키지 못하는 상황이 발생하게 된다.
Algorithm 3 (Peterson's Algorithm)
- turn과 flag, 2개 변수를 모두 사용하고 있다.
- 상대방이 들어갈 차례이고(turn), 들어갈 의도를 표하고 있다면(flag) while문에서 기다리게 된다.
- 상대방이 들어갈 차례가 아니고 크리티컬 섹션에 관심이 없다면 들어갈 수 있다.
- 상대방이 들어갈 차례라도 크리티컬 섹션에 관심이 없다면 들어갈 수 있다.
- 상대방이 들어갈 차례가 아니고, 크리티컬 섹션에 관심이 있더라도 들어갈 수 있다.
- Mutual Exclusion, Progress, Bounded Waiting 모두 만족하는 알고리즘이다.
- Busy Waiting(==Spin Lock) 문제가 있다.
- 계속 CPU와 Memory를 쓰면서 wait한다.
- 어떤 프로세스가 이미 크리티컬 섹션에 들어간 상태에서, 다른 프로세스가 CPU를 잡으면 while문을 돌게 된다. 계속 본인의 CPU 할당시간을 낭비하게 된다.
- 이러한 고급 언어 문장가 실행되는 도중에, 한 문장에 인스트럭션이 여러개 있으므로 중간 중간에 CPU를 빼앗길 수 있기 때문에, 동기화를 보장하기 위해 코드가 조금 복잡하게 짜여졌다.
- 즉 락을 거는 도중에도 CPU를 뺏길 수가 있다. 락을 거는 고급 언어 문장이 여러 인스트럭션으로 짜여져 있을 수 있으므로.
- 인스트럭션 하나를 진행할 때는 인터럽트가 당연히 뺏기지 않는다.
Synchronization Hardware
- 하드웨어적으로 하나의 인스트럭션만 주어지면, 이러한 크리티컬 섹션 문제는 쉽게 해결된다.
- test_and_set(a)
- a라는 데이터의 현재 값을 읽어내고, a라는 데이터의 값을 1로 바꿔주는 과정을 하나의 인스트럭션으로 처리하는 것이다.
- ex. a가 0이었다면, 0이 읽히고 a의 값이 1로 바뀌게 된다.
- ex. a가 1이었다면, 1이 읽히고 a의 값은 원래 1이었지만 다시 1로 셋팅한다.
- 그냥 데이터를 읽고 1로 바꾸는 것이다.
- 이 2가지 작업을 하나의 인스트럭션으로 atomic하게 처리한다.
- 이 함수를 통해 코드를 더 간단하게 바꿀 수 있다.
- lock이 0이었다면, 아무도 크리티컬 섹션에 없다는 것으로 0이 읽히고 크리티컬 섹션에 들어가게 되며 lock을 1로 만들게 된다.(락을 건다.)
- lock이 1이었다면, 1이 읽히고 계속 while문을 돌게 된다. 다른 프로세스가 0으로 만들어주면(락을 푼다) 그 때 들어가게 된다.
- 이러한 과정이 조금 복잡할 수 있으므로, 프로그래머 입장에서 더 추상화된 개념을 사용할 수 있게 해주는 것이 있는데 이것이 Semaphores(세마포어)이다.
6-2강, Process Synchronization 2
Semaphores, 세마포어
- 앞의 방식들을 추상화시키는 것이다.
- Semaphore S
- integer variable, 정수 추상 자료형
- 변수 S는 정수값을 가질 수 있다.
- 자원, 즉 공유 데이터의 갯수라고 생각하면 된다.
- 두 가지 연산을 통해 세마포어에 접근할 수 있다. 있다.
- 공유 자원을 획득하고 반납하는 과정을 세마포어가 처리해주는 것이다.
- P(S) : 공유 데이터를 획득하는 과정
- 락을 거는 과정
- S가 0이하인 경우, 자원이 다 뺏긴 상태이다. while문을 계속 돌게 된다.
- 누군가가 자원을 내놓으면, 즉 S가 0이상이면 자원을 획득하게 되고 S--를 한다.
- V(S) : 공유 데이터를 사용하고 나서 반납하는 과정
- 락을 푸는 과정
- 자원을 내놓을 때 S++한다.
- 두 연산은 atomic하게 연산된다고 가정한다.(한 인스트럭션으로 처리된다)
- Busy Waiting 문제는 존재한다.
- Block & Wakeup 방식으로 이 문제를 해결한다.
Block / Wakeup Implementation
- value : 세마포어 값
- L : 세마포어로 인해 잠들어있는 프로세스들을 연결하기 위한 큐
- 세마포어를 획득할 수 없으면 그 프로세스를 Block
- 누군가 세마포어를 쓰고 나서 반납하게 되면 queue의 한 프로세스를 깨운다.(Wakeup)
- 큐는 PCB를 매달아 놓는 형식이다.
Implementation
- P(S) : 자원의 여분이 없으면 block된다. 여분이 있으면 자원을 얻게 한다.
- 자원의 여분이 없다면, 즉 S가 음수가 되면, S.L(세마포어의 리스트)에 연결시킨다.(즉 block시킨다)
- V(S) : 자원을 다 쓰고 나서 반납하는 연산.
- 반납만으로 끝나는 것이 아니라 잠들어있는 프로세스가 있다면 그 중 1개를 깨워주는 과정도 포함되어야 한다.
- S.value++ 해서 자원을 내놓았음에도 불구하고 S.value가 음수라는 것은, 누군가 아직 잠들어있다는 뜻이다.
- 즉 자원을 내놓으면 다른 것이 들어갈 수 있도록 깨워줘야 한다.
- S.value가 양수면 자원의 여분, 프로세스가 접근할 수 있는 자원이 있다는 뜻이고, S.value가 음수라면 누군가 큐에서 기다리고 있다는 뜻이다.
- 즉 S.value는 누군가를 깨워야하는지 말아야할지 알 수 있는 지표이다.
Which is better?
- CPU를 계속 낭비하는 Busy-Waiting보다는, 잠시 잠들게 되는 Block/Wakeup 방식이 대게 더 낫다.
- 하지만 Block/Wakeup 방식은 컨텍스트 스위칭이 상대적으로 더 일어나니(Block에 넣고 다시 깨우면서 프로세스가 자주 바뀌니까) 오버헤드가 커질 수 있다.
Two Types of Semaphores
-
Counting semaphore
- S.value가 2 이상?인 경우이다.
- 자원의 갯수가 여러 개인 경우이다.
- 여분이 있으면 그냥 가져다 쓸 수 있는 방식이다.
- 여분의 자원을 카운팅하는 용도로 Counting semaphore를 사용한다.
-
Binary semaphore(=mutex)
- 자원의 갯수가 1개인 경우이다.
- 0 또는 1의 값을 가지게 된다.
- 주로 mutual exclusion(lock/unlock)에 사용된다.
Deadlock and Starvation
- 세마포어는 원치 않는 문제가 생길 수도 있다.
- Deadlock
- 둘 이상의 프로세스가 서로 상대방에 의해 충족될 수 있는 event를 무한히 기다리는 현상이다.
-
빨간색 동그라미 이후, 다른 프로세스가 가진 것을 서로 기다리는데 상대가 가진 것을 내놓지 않으니 서로 영원히 기다리게 된다.
-
순서를 위와 같이 바꾸면 됨
-
P0이 S를 얻었으면 P1은 P0이 내놓을 때까지 위 빨간색에서 block/wakeup 방식으로 기다리게 된다. 영원히 기다리는 것이 아니다.
-
Starvation : 자원을 자기들끼리 공유하면서, 다른 프로세스는 영원히 자기 차례가 못오게 되는 현상.
6-3강, Process Synchronization 3
Classical Problems of Synchronization
Bounded-Buffer Problem (Producer-Consumer Problem), 생산자-소비자 문제
- 원형 버퍼(공유 버퍼)
- producer, consumer는 프로세스이다.
- producer, consumer가 여러 개 있을 수 있다. 즉 producer 프로세스, consumer 프로세스가 각각 여러 개 있을 수 있다.
- 주황색이, 데이터가 들어 있는 버퍼
- 회색은 데이터가 없는, 즉 비어 있는 버퍼
- 주황색은 생산자(producer)가 데이터를 만들어 넣어 놓은 상태
- 회색은 애초에 비어 있었던지, 생산자가 데이터를 넣었으나 컨슈머가 꺼내간 상태
- 문제
- 생산자 둘이 같이 데이터를 같은 곳에 집어넣으려고 하면 문제가 생긴다.
- 이를 방지하기 위해 먼저 도착한 프로세스가 락을 건다. 다른 어떤 프로세스도 접근하지 못하도록
- 꺼내가는 컨슈머도 마찬가지이다.
- 생산자가 한꺼번에 도착하여 공유 버퍼를 가득 채운 경우
- 또 생산자가 도착한다면 소비자가 나타나 데이터를 꺼내갈 때까지 기다려야 한다.
- 소비자가 모두 꺼내간 경우, 모두 비어버린 경우
- 소비자가 도착해도 생산자가 내용을 만들어 넣어줄 때까지 기다려야 한다.
- 락을 걸고 풀어줘야 한다.
- 이러한 것들을 세마포어를 이용해 해결할 수 있다.
- 락을 걸고 풀어주는 데에, 자원을 카운팅하는 데에 세마포어가 필요하다.
- 생산자 소비자 문제를, 세마포어를 이용한 슈도 코드 형태
- 내용이 들어있는 버퍼를 세기 위한 변수(full), 비어있는 버퍼를 세기 위한 변수(empty), 락을 걸기 위한 변수(mutex)
- mutex가 1이므로 하나의 프로세스만 접근할 수 있다.
- 메커니즘
- P, 자원을 획득하는 과정(자원을 감소시킴) / V, 자원을 반납하는 과정(자원을 증가시킴)
- 생산자 입장에서 자원은 빈 버퍼 / 소비자 입장에서 자원은 데이터가 차 있는 버퍼이다.
- Producer
- 빈 버퍼가 있는지부터 확인. 빈 버퍼가 있으면 하나를 획득하고 P(empty)(empty 자원의 갯수가 감소), 없다면 기다린다.
- 다른 프로세스가 락을 걸지 않은 상태라면, 버퍼 전체에 P(mutex)로 락을 건 다음, 버퍼에다가 데이터를 넣게 된다. 아니면 P연산에서 기다리게 된다. add x to buffer
- 데이터 넣는 것이 끝나면, 락을 푼다. V(mutex)
- 데이터가 차 있는 버퍼 갯수를 1 증가시킨다. V(full)
- Consumer
- 내용이 들어있는 버퍼(없으면 기다린다)를 획득 P(full)(full 자원의 갯수가 감소)
- 공유 버퍼 전체에 대해 락을 건다. P(mutex)
- 공유 버퍼에서 데이터를 꺼내간다. remove an item from buffer to y
- 공유 버퍼 전체에 대한 락을 푼다. V(mutex)
- 비어있는 버퍼의 갯수를 1 증가시킨다. V(empty)
> ### Readers-Writers Problem
- 읽는 프로세스, 쓰는 프로세스로 2가지의 프로세스가 있다.
- 여기서는 공유 데이터를 DB라고 명명한다. 주로 이런 읽고 쓰는 과정이 데이터베이스에서 발생하기 때문에
- 한 프로세스가 DB에 write 중일 때, 다른 프로세스가 접근하면 안된다. 락을 걸어 하나의 프로세스가 접근하게 해준다. 접근이 끝났으면 락을 풀어준다.
- write은 동시에 여럿이 하면 안되지만, read는 동시에 여럿이 해도 된다. DB를 수정, 건드리지 않으니까
- 쉽게 하려면 둘다 락을 걸고 푸는 방식으로 하면 되지만, 그렇게 하면 read는 동시에 해도 되는건데 다른 것들이 read를 못하게 되니까 CPU를 낭비할 수 있다.
- Solution(누군가가 쓰는 동안에는 못 건드리게 락을 걸지만, 누군가가 읽고 있을 때는 같이 읽을 수 있게)
- writer가 DB에 접근 허가를 얻지 못한 상태에서는, 대기 중인 모든 reader들을 다 DB에 접근하게 해준다.
- read할 때 reader가 도착하면 허용
- read할 때 writer가 도착하면 기다린다. write는 아무도 없을 때 해야 한다.
- writer가 DB에 접근 중이면 reader들은 접근이 금지된다.
- writer가 DB에서 빠져나가야 reader의 접근이 허용된다.
- Shared data
- Writer
- 대문자 DB는 공유 데이터(shared data)이다.
- 공유 데이터에 접근하기 위해 락을 건다. P(db). 물론 누군가가 DB를 사용 중이면 P 연산을 통과하지 못하고 기다린다.
- 락이 걸려 있지 않으면 DB에 쓰는 작업을 실행한다. writing DB is performed
- 락을 풀어준다. V(db)
- Reader
- 쉽게 구현하려면 writer처럼 하면 되지만 비효율적이다.
- 하지만 어쨋든 읽을 때에도 락을 걸어야 한다. P(mutex). writer가 와서 읽는 도중에 공유 데이터를 수정할 수도 있으므로!
- 다른 reader가 왔을 때 같이 read할 수 있게 해준다.
- readcount는 공유 변수(공유 데이터)이다. 모든 reader들이 변경할 수 있기 때문이다.
- 내가 처음 읽으러 들어왔다면, 락을 걸고 P(db)
- 최초의 리더가 아니라면, 즉 누군가가 읽고 있다면, readcount는 1이 아닐 것이다. 그냥 읽기만 하면 된다. reading DB is performed
- readcount가 1이 아니라면, 즉 최초의 reader가 아니라면, if문을 통해 P(db)를 할 필요가 없다. 그냥 읽기만 하면 된다. 그냥 reading DB is performed를 하면 된다.
- readcount도 공유 변수이기 때문에, 여러 reader가 와서 readcount를 변경하기 때문에, 2개를 증가시킬려고 해도 1개가 증가될 수 있다.
- 이 readcount를 바꾸기 위해서도, 공유 변수인 readcount에 락을 걸 필요가 있다.
- 이 readcount라는 공유 변수를 위한 락이 mutex라는 세마포어 변수이다.
- 마지막으로 나갈 readcount라면, 마지막 readcount-- 이후에 readcount는 0이 될 것이다. 이 때 락을 풀도록 한다.
- mutex : (reader들끼리) 공유 변수인 readcount를 접근하는 코드인 critical section의 mutual exclusion을 보장하기 위해 사용한다.
- db : reader와 writer가, 공유 DB 자체를 올바르게 접근하기 위해, (writer와 reader 서로) mutual exclusion 보장하기 위해 사용한다.
- 이 코드만으로는 starvation이 발생할 수 있다.
- 어느 정도 reader가 지나가면 reader를 막고 writer가 지나갈 수 있게도 해줘야 한다.
- 그저 여러 reader가 읽기가 가능하도록 하는 것을 보여주기 위한 것에 초점을 맞춘 코드이다.
Dining-Philosophers Problem
- 세마포어 구현(모니터로 구현하는 것이 더 쉽다고 한다)
- 철학자가 하는 일은 2가지
- 생각하는 일
- 생각하다가 배고파져서 밥 먹는 일
- 배고파지면 자신의 왼쪽, 오른쪽 젓가락을 잡아서 밥을 먹는다.
- 배가 불러지면 젓가락을 놓고 생각한다.
- 즉 왼쪽, 오른쪽 젓가락을 잡아 밥을 먹고, 생각하는 일을 반복한다.
- 공유 자원이기에, 밥을 먹기 위해 젓가락을 잡는데 못 잡는 상황이 발생할 수 있다. P 연산에서 기다리게 된다.
- 밥을 먹고 나면 V 연산을 통해 젓가락을 놓게 된다.
- Dead Lock의 가능성이 있다.
- 아무런 상황이 진행이 안되고 무한하게 멈춰 있는 상황.
- 모든 철학자가 동시에 배가 고파져서, 왼쪽 젓가락을 서로 집어버리는 경우
- 해결 방안
- 4명의 철학자만 테이블에 동시에 앉을 수 있도록 한다.
- 젓가락을 모두 다 집을 수 있을 때만 집을 권한을 준다. 둘 중 1개라도 집을 수 없으면 권한을 주지 않는다.
- 비대칭
- 짝수 철학자는 왼쪽 젓가락부터 집도록
- 홀수 철학자는 오른쪽 젓가락부터 집도록
- self[i] = 0은, 지금 i가 공유 자원 접근 권한이 없음을 의미한다.
- 반대로 1이면 공유 자원 접근 권한이 있음을 의미한다.
- test를 통해 현재 공유 자원 접근 권한을 얻을 수 있는지 확인한다.
- test를 통해 self[i]의 값을 1 증가시킨다.
- (if문) 자신의 왼쪽, 오른쪽이 일하는 중이 아니라면, 그리고 현재 프로세스가 공유 자원 접근 권한을 얻으려고 하면
- 그 다음 원래 pickup으로 돌아가 공유 변수에 대한 락을 풀고(공유 변수 접근은 끝났으니)
- 공유 자원 접근 권한을 얻도록 한다.
Monitor
-
모니터 내부에, 공유 데이터에 접근하는 프로시저가 선언되어 있다.
-
세마포어의 문제점
- P, V 연산으로 코딩을 쉽게 해주었지만, 그럼에도 불구하고 코딩하기 힘들다.
- 실수를 유발하기 쉽다.
- 정확성을 입증하기 어렵다.
- 따라서 더 나은 모니터를 통해 synchronization을 보장하고 있다.
-
모니터
- 동시 수행 중인 프로세스 사이에서, 추상적인 data type의 안전한 공유를 보장하기 위한, 프로그래밍 언어에서 지원하는 high-level synchronizaion construct
- 객체 지향적
- 공유 데이터를 접근하기 위해서는, 모니터라고 정의된 구조체(객체)의 내부 프로시저를 통해 공유 데이터를 접근할 수 있게 한다.
- 공유 데이터에 접근하기 위해서는, 모니터 내부의 프로시저(operations)을 통해 접근할 수 있게 한다.
- 모니터 내부의 프로시저는 동시에 여러 개가 실행이 안되도록 통제된다.
- 따라서 프로그래머 입장에서는 공유 데이터에 접근할 때 세마포어처럼 락을 걸어 코딩할 필요가 없어진다.
- 모니터 자체가 그렇게 만들어졌다.
- 하나의 프로세스만 모니터에 접근할 수 있게 된다.
- 락을 거는 것은 할 필요가 없지만, 자원이 없으면 기다리게 하고, 자원이 있으면 접근하게 하는 연산은 여전히 필요하다.
- 세마포어 변수처럼 비슷한 역할을 해주는 것이 condition variable이다.
-
모니터에서는 락을 걸거나 풀 필요가 없게 되어 있다.
-
모니터 안에서 하나의 프로세스만 활성화된다. 나머지는 entry queue에서 기다린다.
-
모니터 안으로 들어갔더라도, 각자 필요한 공유 자원이 없다면 또 각자의 대기 줄에서 기다리게 된다.
-
생산, 소비 모두 모니터 내부에서 실행할 수 있다.
- 생산자 입장
- 비어 있는 버퍼가 없다면
- 비어 있는 버퍼를 기다리는 줄에서 기다리게 blocked 상태로 보낸다. empty.wait()
- 비어 있는 버퍼가 있다면 그 버퍼에 내용을 넣어주면 된다. add x to an empty buffer
- 그 작업이 끝나면, 내용이 없어 잠들어 있는 소비자가 있다면 깨워준다. full.signal()
- 소비자 입장
- 차 있는 버퍼(내용이 들어 있는 버퍼)가 없다면
- 차 있는 버퍼를 기다리는 줄에서 기다리도록 blocked 상태로 보낸다. full.wait()
- 차 있는 버퍼가 있다면 그 버퍼의 내용을 지우고 x에다가 저장한다. remove ~
- 비어 있는 버퍼를 기다리는 생산자를 깨워준다. empty.signal()
6-4강, Process Synchronization 4
- 모니터 버전으로 Dining Philosophers 문제 해결
궁금한 점
- atomic하게 연산한다라는게 내가 생각하는 그것이 맞나
- 생산자/소비자 문제
- P랑 V가 정확히 무엇? 자세히 어떤 걸 얻고 반납하는가?
- 지금 이거 다 프로세스, 쓰레드 기준? 주체가 무엇인거지?
- P(db), P(mutex)의 차이
- 인터럽트
- reader에서도 P(db)를 통해 db에 락을 거는데, 다른 reader가 와서 db를 읽을 수 있나?
팀 스터디에서 얻은 지식
- Shared Data (공유 데이터): 여러 프로세스들이 서로 공유할 수 있는 데이터
- Critical section (임계 영역): 공유데이터를 동시에 접근하는 코드 영역. 공유 데이터로 들어가는 코드 영역