여러 프로세스들이 동시에 critical section에 진입하는 것을 막기위해 필요하다.
critical section(임계 구역)
은 코드 상에서 race condition이 발생할 수 있는 부분이다race condition(경쟁 상태)란 두 개 이상의 concurrent한 프로세스(혹은 스레드)들이 하나의 자원(리소스)에 접근하기 위해 경쟁하는 상태
어떤 프로세스가 critical section을 수행 중이면 다른 모든 프로세스들은 critical section에 진입할 수 없다.
아무도 critical section에 없을 경우 critical section에 들어가고자 하는 프로세스가 있으면 들어가게 해주어야한다.
crtical section에 들어가고자 하는 프로세스의 대기시간이 유한적이어야 한다.
- 즉, 프로세스가 critical section에 들어가기 위해 무한대기를 하면 안된다.
Algorithm 1
두 개의 프로세스 P_i, P_j가 있다고 가정하자.
Synchronization variable
int turn;
intially turn=0; -> turn==i면 P_i는 critical section에 들어갈 수 있다
P_i's code
do{
while(turn!=i); // my turn?
critical section
turn =j; // now it's your turn
remainder section
}while(1);
Mutual exclusion
은 만족하지만 progress
는 만족하지 못한다.반드시 한 번씩 교대로 들어가야 하기 때문에 특정 프로세스가 더 자주 critical section에 들어가야 한다면 대기시간이 길어지는 문제가 발생한다.
Algorithm 2
두 개의 프로세스 P_i, P_j가 있다고 가정하자.
Synchronization variable
boolean flag[2]; -> 어떤 프로세스가 critical section에 들어갈 지 결정
intially falg[all]=false; -> 어떤 프로세스도 critical section에 들어갈 수 없음
- flag[i]==true면 Pi는 critical section에 들어갈 수 있다.
P_i's code
do{
flag[i]=true; // critical section 진입 요구
while(flag[j]); // P_j의 flag 확인
critical section
flag[i]=false; // crtical section 종료
remainder section
}while(1);
Mutual exclusion
은 만족하지만 progress
는 만족하지 못한다.flag[i], flag[j]가 모두 true라면 두 프로세스 모두 critical section에 들어가지 못하고 끊임없이 대기하는 상황이 발생한다.
Algorithm 3(Peterson's Algorithm)
위의 두 알고리즘을 합친 방법으로 turn, flag 변수 모두 사용한다.
P_i's code
do{
flag[i]=ture; // critical section 진입 요구
turn=j; // 상대방 turn으로 변경
while(flag[j]&&turn==j); // 상대방의 turn이고, 상대방이 critical section 진입을 요구하면 대기한다
critical section
flag[i]=false;
remainder section
}while(1);
하지만 계속 CPU와 Memory를 사용하면서 critical section 진입을 대기하기 때문에
Busy Waiting(spin lock)
이 발생한다
하드웨어적으로 Test & Modify를 atomic하게 수행할 수 있도록 지원하는 경우 앞의 문제들을 간단히 해결할 수 있다.
atomic hardware instruction의 대표적인 예로는 Test_and_set 등이 있다.
이전까지의 알고리즘들은 데이터를 읽고 쓰는 것을 하나의 명령어로 처리할 수 없었지만, Test_and_set 명령어를 이용하면 데이터를 읽으면서 쓰는 것까지 하나의 명령어로 동시에 수행이 가능하기 때문에 간단하게 lock을 걸고 풀 수 있다.
데이터를 읽어옴과 동시에 lock을 건다
Synchronization variable
boolean lock=false;
P_i's code
do{
while(Test_and_Set(lock)); // 이미 lock이 걸려있는지 확인
critical section
lock=false;
remainder section
}while(1);
세마포어(Semaphores)는 Busy Waiting이 필요 없는 동기화 도구이며 여러 프로세스나 스레드가 critical section에 진입할 수 있는 signaling 메커니즘이다.
앞의 방식들을 추상화함으로써 lock이나 공유자원 counting을 간단하게 하는 방법이다.
P(S){
while(S<=0 do no-ops);
S--;
}
자원 S가 양수라면 자원을 할당받고 자원의 개수 감소
V(S){
S++;
}
Synchronization variable
semaphore mutex; -> intially 1: 1개의 프로세스가 critical section에 들어갈 수 있다.
P_i's code
do{
P(mutex);
critical section
V(mutex);
remainder section
}while(1);
대기하는 과정에서 Busy Waiting의 발생으로 비효율적이기 때문에 Block & Wakeup
방식을 사용한다.
Block
- 커널은 block을 호출한 프로세스를 suspend 시킴
- 해당 프로세스의 PCB를 semaphore에 대한 wait queue에 삽입
wakeup(P)- block된 프로세스의 P를 wakeup 시킴
- 해당 프로세스의 PCB를 ready queue에 삽입
typedef struct{
int value; // 세마포어 변수
struct process *L; // block된 프로세스들의 대기 Queue
}semaphore
void P(semaphore S){
S.value--;
if(S.value<0){
add this process to S.L; // block queue에 추가
block(); // 자원이 없다면 block 상태로 진입
}
}
void V(semaphore S){
S.value++;
if(S.value<=0){
remove a process P from S.L;
wakeup(P); // 자원을 기다리는 프로세스를 깨워줌
}
}
S.value++ 로 자원을 내놓았음에도 불구하고 자원이 0이하라면 자원을 기다리고 있는 프로세스가 존재한다는 의미이다.
세마포어에는 두 가지의 타입이 존재하는데
1. Counting semaphore
도메인이 0 이상인 임의의 정수값
주로 resources counting에 사용된다.
2. Binary semaphore(mutex)
bool 값만 가질 수 있다
주로 mutual exclusion(lock/unlock)에 사용된다.
Producer-Buffer-Consumer 구조일 때 생기는 문제점
두 경우 모두 버퍼에 접근할 수 없도록 락을 걸어주어야 한다.
Synchronization variable
mutual exclusion: binary semaphore
resource count: integer semaphore(full/empty 버퍼의 개수)
semaphore full=0, empty=n, mutex=1(lock)
do{
produce an item in x
P(empty); // 빈 버퍼가 있으면 획득 없으면 wait
P(mutex); // lock
...
add x to buffer // 버퍼에 데이터 삽입
...
V(mutex); // unlock
V(full); // full buffer count++
}while(1);
do{
P(full); // 데이터가 들어있는 버퍼가 있으면 획득 없으면 wait
P(mutex); // lock
...
remove an item from buffer to y // 데이터 추출
...
V(mutex); // unlock
V(empty); // empty buffer count++
...
consume the item in y
...
}while(1);
한 프로세스가 데이터에 대해 write 작업을 수행할 떄 다 른 프로세스가 접근하면 안되고 read 작업은 동시에 여러 프로세스가 수행 가능하도록 하는 문제
solution
Synchronization variable
shared data
- int readcount=0, data
semaphore mutex=1, db=1- db: 공유 data에 대한 lock/unclok
- mutex: 공유변수 readcount에 대한 lock/unlock
do{
P(db); // reader가 없다면 lock
...
writing data is perforemd
...
V(db); // unlock
}while(1);
do{
P(mutex); // readcount lock
readcount++;
if(readcount==1) P(db); // 최초의 reader라면 data lock
V(mutex); // readcount unlcok
...
reading data is performed
...
P(mutex); // readcount lock
readcount--;
if(readcount==0) V(db); // 마지막 reader라면 data unlock
V(mutex); // readcount unlock
}while(1);
계속해서 writer나 reader가 들어오는 경우 한 쪽이 계속 대기하는 starvation
이 발생할 수 있다
큐에 우선순위를 두거나 timer를 통해 write와 read를 번갈아가면서 하도록 구현한다면 해결할 수 있다.
5명의 철학자가 원탁에 둘러앉아있고, 5개의 젓가락이 있다
Synchronization variables
semaphore chopstick[5];
- intially all values are 1
do{
P(chopstick[i]); // 왼쪽 젓가락을 잡는다
P(chopstick[(i+1)%5]); // 오른쪽 젓가락을 잡는다
...
eat();
...
V(chopstick[i]); // 왼쪽 젓가락을 내려놓는다
V(chopstick[(i+1)%5]); // 오른쪽 젓가락을 내려놓는다
...
think();
...
}while(1);
위와 같이 구현하면 매우 위험한 부분이 있다.
Deadlock의 가능성
모든 철학자가 동시에 배가 고파서 왼쪽 젓가락을 집은 경우
- 다른 철학자가 오른쪽 젓가락을 집을 수 없다.
solution
1. 4명의 철학자만이 테이블에 동시에 앉을 수 있도록 한다.
2. 젓가락을 두 개 모두 집을 수 있을 때에만 젓가락을 집을 수 있게 한다
3. 짝수/홀수 철학자는 왼쪽/오른쪽 젓가락을 먼저 집도록 한다.
세마포어의 문제점
1. 코딩하기가 힘들고
2. 정확성을 입증하기가 어렵다
3. 자발적 협력이 필요하다
4. 한번의 실수가 모든 시스템에 치명적 영향
V(mutex)와 P(mutex)의 순서에 따라 deadlock이 생기거나 mutual exclusion이 깨질 수 있다.
Monitor(모니터)는 이러한 세마포어의 단점을 보안하기 위한 방법으로
- 동시 수행중인 프로세스 사이에서 추상 데이터의 안전한 공유를 보장하기 위한 High-level 동기화 구조이다.
- 공유 데이터를 접근하기 위해서는 모니터 내부 procedure를 통해서만 접근할 수 있다.
- lock을 걸 필요가 없다.
프로세스는 오직 모니터 내부의 프로시저를 통해서만 공유 데이터에 접근할 수 있다. 또한 한 번에 하나의 프로세스, 스레드만 모니터에 접근할 수 있다
모니터에 접근중인 프로세스가 있으면 다른 프로세스들은 모니터 큐(Monitor Queue)
에서 대기한다.
이 때 조건변수가 사용된다
조건 변수는 어떤 값을 가지는게 아니라 프로세스를 sleep 혹은 wakeup 시키는 역할을 한다
조건 변수는 오직 wait와 signal 연산에 의해서만 접근 가능하다
공유된 자원의 데이터를 여러 프로세스에서 접근하는 것을 막기 위한 추상 데이터.
- 세마포어의 값은 자원의 상태를 나타내는 간단한 카운터이다.
상호배제라고도 하며 crtical section을 가진 스레드의 runnig time이 서로 겹치지 않도록 각각 단독으로 실행하게 하는 기술.
- 프로세스는 세마포어를 사용하고 스레드는 뮤텍스를 사용한다
가장 큰 차이는 동기화 대상의 개수이다
- 뮤텍스는 대상이 하나
- 세마포어는 대상이 하나 이상일 때
참고)