뮤텍스와 세마포어
뮤텍스 락
임계 구역 문제를 조금 더 하이레벨에서 해결하기 위해 Mutex Lock을 통해 상호배타만이라도 해결해보도록 한다. 임계 구역에 들어가려면 열쇠를 가지고 다시 나올 때면 이를 반납하도록 한다. 가장 간단한 방법이다. 여기서 더 발전하여 세모포, 모니터, 라이브니스와 같은 도구들이 나오게 되었다.
지금은 가장 기본이 되는 뮤텍스 락을 알아보자. mutex는 mutual exclusion의 줄임말로 상호배제를 통해 임계구역을 보호하고 경쟁 상태를 막는다. 이것을 사용하기 위해서는 acquire(), release(), available(boolean)만 있으면 된다. 참고로 이 함수는 atomical하게 구현하여야 한다.
하지만 이렇게 간단하게 구현하면 하나의 문제가 생기는데 이것이 busy waiting 문제가 생긴다. acquire() 하는 동안 끊ㅁ임없이 무한루프를 돌기 때문에 이는 쓸데없는 CPU 소모가 생겨 다른 프로세스의 시간을 빼았게 된다. 이렇게 busy waiting을 하는 뮤텍스 락을 spin lock이라고 한다. 그런데 이 spin lock이 유용할 때가 있다. 보통 프로세스는 spin lock을 안하기 위해서 wait 상태가 되어야 하고 다시 컨텍스트 스위칭을 하기 위해서는 레디 큐로 들어가서 CPU를 획득해야 하는 상당히 비효율적인 과정을 거쳐야 한다. 하지만 spin lock은 CPU를 계속 획득하고 있는 상태이기 때문에 이러한 과정이 생략되어 상당히 빠르게 컨텍스트 스위칭이 이루어진다. 따라서 코어가 여러 개 있는 멀티코어 환경에서는 이러한 spinlock이 굉장히 유용할 때가 많다.
세마포어
지금까지 뮤텍스 락은 2개의 프로세스가 어떻게 임계 구역 문제를 해결하는지 살펴보았다. 세마포어는 프로세스가 n개 있을 때를 해결하기 위한 방법이다. 세마포어는 기찻길에 있는 신호장치이다. 명칭에서 알 수 있듯이 기차가 지나갈 수 있도록 신호를 제어하는 장치처럼 세마포어는 프로세스가 임계 구역에 갈 수 있는지 제어하는 장치이다.
세마포어는 wait()와 signal(), S(int)를 통해서 구현할 수 있다. 마찬가지로 wait()와 signal()은 atomical하게 설계해야 한다.
세마포어 또한 busy waiting 문제가 발생할 수 있다. 이 문제릃 해결하기 위해서 P()와 V()의 정의를 수정할 수 있다. 만약 어떤 프로세스가 wait을 할때 무한 루프로 계속 돌리는 것이 아니라 waiting queue에 들어가게 하고 signal이 호출되면 이 프로세스를 깨워서 레디 큐에 넣어주면 된다.
모니터와 자바 동기화
세마포는 굉장히 편리한 도구인 것은 맞지만 프로그래밍 에러가 발생할 수 있다. 실행단계에서 문제가 생기면 에러를 찾기도 잡기도 어렵다. 예를 하나 들어보면 만약 wait와 signal의 순서가 바뀌어서 signal이 먼저 이루어진다면 경쟁 상태가 일어난다. 프로그램이 복잡해지면 이와 같은 문제가 빈번해질 수 있다.
모니터는 세마포와 뮤텍스를 사용할 때 생기는 문제를 해결하기 위한 도구이다. 모니터는 상호배제를 제공해주는 하나의 데이터 타입이다. 하나의 클래스라고 이해하면 쉽다. 아래의 수도 코드를 보면 하나의 모니터로 자료구조를 묶어주면 동기화가 자동으로 이루어진다.
여기에 모니터는 conditional variable을 활용한다. conditional variable이란 특정 조건을 만족하기를 기다리는 변수라는 의미이다. conditional variable은 wait와 signal을 사용한다. 이것을 이용하여 스레드 간의 신호 전달을 할 수 있는 것이다.
자바는 특히 모니터를 잘 이용하는 언어이다. 자바에서는 스레드 동기화를 위해 monitor-lock을 사용한다. 자바에서의 동기화를 알기위해서는 synchronized 키워드와 wait(), notify() 메서드를 살펴보아야 한다.
우선 synchronized 키워드는 임계영역에 해당하는 코드 블록을 선언할 때 사용되는 일종의 자료구조이다. synchronized를 이용해 임계 구역으로 지정되면 해당 영역에는 모니터락을 획득해야 진입이 가능하다. 또한 이를 메소드에 선언하면 메소드 코드 블록 전체가 임계 영역으로 지정된다. 이 때, 모니터락을 가진 객체 인스턴스는 this 객체 인스턴스이다.
synchronized를 통해 임계 구역을 지정하여 상호배제는 해결하였지만 어떤 프로세스가 임계 구역에 들어갈 수 있을지를 결정할 수 있어야 한다. 이를 위해 wait(), notify() 메서드가 사용된다. 스레드가 어떤 객체의 wait()를 호출하면 해당 객체의 모니터락을 획득하기 위해 대기 상태로 진입하게 되고, 임계 구역에서 할 일이 끝나다면 notify()를 호출하여 대기 중인 스레드 하나를 깨운다. 만약 notifyAll()을 호출하면 해당 객체 모니터에 대기중인 스레드를 모두 깨운다.
Liveness
세마포어와 모니터는 상호배타 문제를 해결할 수는 있지만 여전히 진행과 유한 대기 문제는 해결할 수가 없다. 오히려 상호배타 문제를 해결하려다 보니 데드락 문제가 더 발생할수도 있게된다. Liveness는 이러한 문제를 모두 해결하기 위해 최근에 등장한 방법이다. 두 가지 상황이 Liveness를 실패로 이끌 수 있는데 한 번 살펴보자.
첫번째는 Deadlock이다. 데드락은 두 개 이상의 프로세스가 무한하게 대기하는 현상을 말한다. 이는 대기 큐에 있는 프로세스를 기다리면서 생기게 된다. 예를 들어 아래와 같은 코드가 있다고 해보자.
P0에서는 세마포어 S를 기다리고 P1에서는 세마포어 Q를 기다린다고 해보자. 이렇게 되면 서로의 세마포어가 릴리즈 될 때까지 무한정 대기하게 되는데 이것이 데드락이다.
두번째는 Priority Inversion이다. 우선순위 역전이라는 것인데, 높은 우선순위를 가진 프로세스(상)가 낮은 우선순위의 프로세스(하)가 끝날 때까지 계속해서 대기하게 되는 현상이다. 하가 세마포어를 획득하였는데, 상이 실행되면 당연하게도 ㅅ상이 CPU를 갖고 먼저 실행되게 된다. 하지만 만약 상 또한 세마포어가 필요한 프로세스였다면 어떻게 될까? 하가 세마포어를 가지고 있는 상태로 대기 큐로 들어가버렸으니 상은 하가 세마포어를 릴리즈 할 때까지 블록되어 버리고 실행하지 못하게 된다.
이는 priority inheritance 즉, 우선순위 상속을 통해 해결할 수 있다. 하에게 높은 우선순위를 부여하여 먼저 실행되게 하여 세마포어를 릴리즈하게 하는 것이다. 그렇게 되면 하가 실행이 종료되고 세마포어를 릴리즈해주면서 상이 세마포어를 얻어 실행할 수 있게 되는 것이다.
'CS 지식 > 운영체제' 카테고리의 다른 글
운영체제 Ch8 - 데드락 (0) | 2022.01.03 |
---|---|
운영체제 Ch7 - 동기화 문제의 예시 (0) | 2021.12.13 |
운영체제 Ch6 - 프로세서 동기화(1) (0) | 2021.12.12 |
운영체제 Ch5 - CPU 스케줄링 (0) | 2021.12.08 |
운영체제 Ch4 - 스레드 (0) | 2021.12.08 |