이전 프로세스 동기화 에서 프로세스 혹은 스레드가 공유 리소스에 동시에 접근하는 경우 공유 리소스에 대한 데이터 불일치 문제가 발생하고, 동기화를 통해 공유 리소스에 대한 데이터 일관성을 유지할 수 있는 것을 확인했다.
그리고, 이런 공유 리소스에 접근하기 위해 프로세스끼리 경쟁하는 것을 경쟁 상태(Race Condition), 공유 리소스에 접근하며 실제 동기화 문제가 발생하는 영역을 임계 영역( Critical Section)이라 했다.
마지막으로 임계 영역 문제를 해결하기 위해선 반드시 3가지 조건이 충족되어야 한다고 했다.
- Mutual Exclusion (상호 배제) : 임계 영역에는 반드시 하나의 프로세스만 실행되어야 한다.
- Progress (진행) : 임계 영역이 비어있는 상태라면 대기하고 있는 프로세스가 진입해야 한다.
- Bounded Waiting (유한한 대기) : 임계 영역에 들어가고자 하는 프로세스는 경쟁 상태에서 지더라도 언젠가 경쟁에서 이겨 임계 영역으로 진입해야 한다. 즉 무한히 대기 상태에 있지 않는다.
이번에는 임계 영역 문제를 해결하기 위한 해결책을 확인해보자.
뮤텍스 (Mutex)
Mutual Exclusion의 앞 글자를 딴 것으로 멀티 스레드 환경에서 공유된 리소스에 대한 접근을 제한하기 위한 동기화 매커니즘이다.
대기하는 스레드는 Busy Waiting을 하면서 대기하고, 임계 영역에 들어간 스레드가 공유된 리소스를 반환하고 락을 해제하면 대기하던 스레드가 락을 획득한다.
이렇게 무한루프를 돌면서 락을 체킹하는 것을 스핀락 (Spin Lock)이라 부른다.
acquire() {
while(!available) ; // Busy Waiting
available = false; // 락을 획득
}
release() {
available = true; // 락을 해제
}
do {
acquire();
// 임계 영역에 들어감 (락 획득)
release();
// 임계 영역을 떠남 (락 해제)
} while(true);
스핀락 (Spin Lock)
스핀락 (Spin Lock)은 단어에 내포한 뜻 그대로 루프를 돌면서 Lock을 체크한다.
- 두 개의 프로세스 A, B가 있다.
- 만약 A가 B와의 경쟁에서 이겨 공유 리소스를 점유했다면, A는 공유 리소스에 대한 락을 얻는다.
- 이 때, 경쟁에서 진 B는 Lock을 얻을 때 까지 기다린다.
B는 무한루프를 돌면서 Lock을 얻기 위해 대기하는 데 이를 “바쁘게 대기한다.”라고 하여 Busy Waiting이라고 한다.
Busy Waiting 상태에 있으면 다른 프로세스에게 CPU 점유를 내주지 않고 Context Switching이 발생하지 않는다.
만약, 임계 영역에 대한 Lock 빠른 시간 안에 다른 프로세스에게 넘어간다면 아주 짧은 시간 동안 Busy Waiting을 하면서 Context Switching이 일어나지 않아 효율적일 수 있지만, Lock을 점유하는 시간이 장기적이라면 Busy Waiting동안 CPU를 100% 점유하고 있기 때문에 효율성이 떨어진다.
따라서, 스핀락은 Lock의 점유 시간이 길거나, CPU가 한 개인 상황에서는 유용하지 않다.
세마포어 (Semaphore)
세마포어는 두 개의 원자적인 함수로 컨트롤 되는 정적 변수로서 스레드끼리 공유되는 변수이다.
간단하게 설명하면 프로세스가 임계 영역을 빠져나오면서 변수를 증가시켜주는 signal 함수와 임계 영역 진입을 하기 위해 변수를 감소시키고 대기하는 wait 함수를 하나의 변수를 이용해 컨트롤 한다.
Busy Waiting 방법
기존의 세마포어는 Busy Waiting을 이용했다.
- 만약 사용할 수 있는 리소스가 1개이고 현재 리소스를 아무도 사용하고 있지 않다면 스레드 A는 대기하지 않고 임계 영역으로 진입하고 S값을 감소시킨다.
- 스레드 B가 진입했는데 사용할 수 있는 리소스가 없다. while문 조건에 걸리기 때문에 Busy Waiting을 하며 대기한다.
- 스레드 A가 임계 영역을 빠져나오면서 리소스를 증가시킨다. 스레드 B는 리소스가 생김과 동시에 다시 S 값을 감소시키고 임계 영역으로 진입한다.
wait(S) {
while(S <= 0) // Busy Waiting (리소스가 없으면 대기)
S--; // 리소스를 획득하면 변수를 감소
}
signal(S) {
S++; // 임계 영역에서 빠져나올 때 리소스를 반환하면서 변수를 증가
}
Block - wakeup
Busy Waiting 방식을 보완한 방식으로 써 대기 큐(Waiting Queue)를 이용한 방식이다.
- 기존에는 스레드가 대기하면서 계속해서 진입을 체크했다면, block - wakeup 방식은 스레드가 리소스를 획득할 수 없다면 대기 큐에 넣어놓고 재운다.
- 만약 리소스를 사용하던 스레드가 리소스를 반환한다면 리소스 개수를 증가시키고, 대기 큐에 있는 한 개의 프로세스를 가져와서 깨운다.
wait(S) {
S.value--; // 리소스를
if(S.value < 0) {
대기 큐에 프로세스를 넣어준다.
block(); // 프로세스를 재운다.
}
}
signal(S) {
S.value++;
if(S.value <= 0) {
대기 큐에서 프로세스 한개(P) 를 제거한다.
wakeup(P) // 제거한 프로세스를 깨운다.
}
}
세마포어의 종류
- 이진 세마포어 (Binary Semaphore) : 0과 1의 값만 가지는 세마포어로서 Mutex로 사용할 수 있다.
- 계수 세마포어 (Counting Semaphore) : 초기값이 초기에 진입할 수 있는 리소스의 개수로 정해진 세마포어이다.
뮤텍스 VS 세마포어
- 뮤텍스는 동기화 대상이 1개인 경우에만 사용이 가능하지만, 세마포어는 동기화 대상이 1개 이상인 경우에도 사용 가능하다.
- 뮤텍스는 공유 리소스를 Lock을 통해 자신이 소유하고 임계 영역을 나갈때 락을 해제할 수 있지만, 세마포어는 signal 함수를 통해 락을 획득하지 않은 스레드가 락을 해제하는 것이 가능하다.
📄 References
[운영체제] Mutex 뮤텍스와 Semaphore 세마포어의 차이 : https://heeonii.tistory.com/14
Operating System Concepts - 9th Edition
'Computer Science > OS' 카테고리의 다른 글
프로세스 동기화 (Process Synchonization) (0) | 2022.07.03 |
---|---|
동기와 비동기, 블로킹과 논블로킹 (0) | 2022.06.12 |
프로세스, 스레드 (0) | 2022.06.07 |