Race Condition (경쟁 상태)
연산부와 저장부의 독립적 존재
여러 연산부가 하나의 저장부를 공유하는 경우 Race Condition(경쟁 상태) 문제 발생 가능
데이터의 최종 연산 결과는 마지막에 그 데이터를 다룬 프로세스에 의해 달라진다.
kernel의 데이터 : 일종의 전역 변수임. (여러 Process가 공유)
한 Process가 kernel의 데이터를 변경하는 중 interrupt 발생 시 문제 발생 가능
위의 문제와 비슷하나 더 복잡하다.
다른 CPU 간의 메모리 데이터 간섭 문제 발생
Process Synchronization
공유 데이터(Shared Data)의 동시 접근(Concurrent Access)는 데이터 불일치 문제(Inconsistency) 발생
일관성(Consistency) 유지를 위해서 협력 프로세스 간 실행 순서를 정해주는 메커니즘이 필요
n개의 프로세스가 공유 데이터를 동시에 사용하기를 원하는 경우, 각 프로세스의 code segment 마다 공유 데이터를 접근하는 코드인 ciritical section이 존재한다.
하나의 프로세스가 critical section에 있을 때 다른 모든 프로세스는 (동일 데이터에 접근하는) critical section에 진입하지 못하도록 한다.
Synchronization Variable : turn
critical section 진입 전 turn 여부를 검사
(turn을 통해 차례 검사)
// turn = 0으로 초기화.
// turn == i(자신의 차례면) 면 critical section 진입
int turn = 0;
do {
while (turn != 0) :
critical_section();
// turn 넘겨 줌
turn = 1;
remainder_section();
} while (1);
P1은 100번 쓰고 싶음. P2는 한 번 쓰고 싶음.
P2 쓰고 P1 넘겨줌, P2 쓰고 P1 넘겨줌. P2는 다 써서 turn 안 넘겨줌.
-> P2도 놀고 있는 CPU 사용 못함.
프로그램적 해결법의 충족 조건
Mutual Exclusion (상호 배제)
프로세스가 critical section 부분을 수행 중이면 다른 모든 프로세스들은 그들의 critical section에 들어가면 안된다.
Progess (진행)
아무도 critical section에 있지 않은 상태에서 critical section에 들어가고자 하는 프로세스가 있으면 critical section에 들어가게 해주어야 한다.
Bounded Waiting (유한 대기)
프로세스가 critical section에 들어가려고 요청한 후부터 그 요청이 허용될 때까지 다른 프로세스들이 critical section에 들어가는 횟수에 한계가 있어야 한다. (starvation 방지)
가정
- 모든 프로세스의 수행 속도는 0보다 크다.
- 프로세스들 간의 상대적 수행 속도는 가정하지 않는다.
Synchronization Variable : flag
flag 통해서 자신이 critical section 진입 희망을 표현 + 상대방 flag 검사
// turn = 0으로 초기화.
// turn == i(자신의 차례면) 면 critical section 진입
int turn = 0;
do {
// critical section 진입 희망
flag[i] = true;
// 다른 flag 확인
critical_section();
// I'm out
flag[i] = false;
remainder_section();
} while (1);
Synchronization Variable : turn, flag
위에서 사용한 두 변수 모두 사용
세 가지 조건 모두 충족 가능
// turn = 0으로 초기화.
int turn = 0;
do {
// critical section 진입 희망
flag[i] = true;
// 턴 넘기기
turn = j;
// flag
while (flag[j] && turn j) ;
critical_section();
// I'm out
flag[i] = false;
remainder_section();
} while (1);
P(S) :
// 자원 없으면 계속 wait
while (S <= 0) do no-np;
S--;
V(S) :
S++;
Semephore를 다음과 같이 정의
typedef struct
{
// semaphore (자원)
int value;
// process wait queue (대기 프로세스 큐)
struct process *L;
}
block과 wakeup은 다음과 같이 가정
P(S) :
// 일단 빼고 봄
S.value--;
// 뺐더니 음수 (가용 자원 X)
// wait list (일종의 연결 리스트) 추가 후 block
if (S.value < 0)
{
block();
}
V(S) :
S.value++;
// 자원 반환 후 S.value가 0 이하라면
// list에서 하나 끄집어서 깨워줘야 함
// CPU 자원을 기다리고 있다는 뜻이므로
if (S.value <= 0)
{
wakeup(P);
}
Which is Better ?
Busy-wait vs Block/wakeup
- 일반적으로는 Block/wakeup이 유리
- Busy-wait은 지속적으로 CPU 낭비
- Critical section이 매우 짧은 경우
Blcok/Wakeup 오버헤드가 Busy-wait 오버헤드보다 커질 수 있다.
Deadlock
예시
S, Q가 1로 초기화된 Semaphore.
P가 S를 소유, V가 Q를 소유
P는 Q가 없어서 작업 못함.
-> Q를 무한히 기다림... S를 무한히 소유함...
V는 S가 없어서 작업 못함.
-> S를 무한히 기다림... Q를 무한히 소유함...
작업이 끝나야지만 자원을 반환하므로 발생하는 문제
크기가 유한한 공유 버퍼 존재
Producer Process : 데이터 생성, Buffer 입력
Consumer Process : 데이터 소비
여러 생산자(소비자) 존재 시 공유 데이터 inconsitency 발생 가능
각 Buffer가 공유 자원.
자원이 여러 개 (Counting Semaphore)
Mutex Lock (binary Semaphore)
Producer 입장
1. Empty Buffer 존재? (없으면 대기)
2. 공유 데이터에 LOCK
3. Empty Buffer 데이터 입력 및 조작
4. UNLOCK
5. Full Buffer 증가
Consumer 입장
1. Full Buffer 존재? (없으면 대기)
2. 공유 데이터에 LOCK
3. Full Buffer 데이터 입력 및 조작
4. UNLOCK
5. Empty Buffer 증가
Semaphor의 문제점
동시 수행중인 프로세스 사이에서 Abstract data type의 안전한 공유를 보장하기 위한 High-level Synchronization Constructs