OS 는 여러가지 동기화 기법을 제공한다.
동기화 기법이 왜 필요할까?
가장 큰 이유는 공유하는 자원에 대하여 두개 이상의 프로세스가 접근할 때 문제가 생기지 않아야 하기 때문이다. 대표적인 문제로 race condition 을 들 수 있는데, 말 그대로 프로세스간의 경쟁이 일어나는 상황이다.
위 예시에서 각 프로세스가 공유하는 Bank 라는 자원에 대하여 1을 더하는 동작을 수행하기 때문에 결과는 19여야 하지만, 동기화가 적절히 이루어지지 않는다면 위와 같은 문제가 발생하는 것이다.
spinlock
공유자원에 대하여 lock 을 취득한 프로세스만 이와 관련한 작업을 처리할 수 있도록 해주는 OS 동기화 기법이다. 이때 프로세스는 lock 을 취득하기 위해 busy-waiting 한다. 즉 sleep 하지 않고 time slice 를 모두 소모할 때 까지 lock 을 획득하려고 시도한다.
위 함수는 spinlock 이 어떻게 동작하는지 알기 쉽게 표현되어 있는 함수라고 생각하면 된다. 프로세스는 while 루프를 돌며 test_and_set 함수를 연속적으로 호출하는데, 만약 lock 값이 0 일 때 이를 1로 변경하였다면 이는 해당 프로세스가 lock 을 취득한 것으로, critical section 에 진입하게 된다.
이 때 생기는 의문점은 멀티 코어의 환경에서 동시에 여러개의 프로세스가 test_and_set 을 호출 했을 때에도 한개의 프로세스만 lock 을 취득할 수 있냐는 것이다. 사실 위 이미지에서 test_ans_set 함수가 3줄짜리 명령어로 이루어져있는 것으로 표현되었지만, 이는 그저 동작의 이해를 돕기 위함이며, 실제로는 이는 한개의 CPU atomic 명령어이다. 즉, 어셈블리어 한줄 짜리 명령어라고 볼 수 있다. 결론적으로, CPU 의 도움을 받아 오직 한개의 프로세스만 lock 을 취득하는 것이 보장된다는 것이다.
단점은 분명하다. 기다리는 동안 CPU를 낭비한다.
mutex
mutex 는 lock 을 취득하지 못한 경우 대기 큐에 해당 프로세스를 추가하고 sleep 하는 방식으로 동작한다. 이후에 lock 을 쥐고있던 프로세스가 이를 해제하며 대기큐에서 기다리던 프로세스를 깨워준. 이러한 방식을 활용하면 spinlock 에서 발생하던 CPU 낭비 문제는 해결될 것이다.
guard 는 mutex value 자체가 공유 자원이기 때문에 사용되는 또 다른 동기화 기법이다. 즉, mutex 동기화 기법을 위한 동기화 기법이라고 볼 수도 있을 것 같다. guard 가 동작하는 방식은 spinlock 이라고 볼 수 있을 것 같다. 하지만, guard 는 mutex value 값을 변경할 때만 잠깐 사용되고, 이를 계속 쥐고있는 프로세스는 존재하지 않기 때문에 CPU 낭비의 문제는 사실상 발생하지 않을 것이다.
mutex 가 spinlock 보다 좋은걸까?
spinlock 은 busy-wating 방식으로 동작하기 때문에 CPU가 낭비될 수 있다. 그렇다면 항상 mutex 가 좋다고 볼 수 있을까? 그렇지는 않다.
물론 싱글 코어 환경이라면 spinlock 이 busy-waiting 하는 동안에 원하는 lock 을 취득할 수 있는 가능성이 사실상 없기 때문에 비효율적이다. 하지만, 멀티 코어환경이라면 한 스레드가 busy-wating 하는 동안 다른 스레드에서 lock 을 release 하여 이를 기다리던 스레드가 바로 lock 을 취득할 수 있다.
이 때 context switching 에 걸리는 시간이 프로세스가 critical section 작업을 수행하는 시간보다 오래 걸린다면, sleep 하지 않고 busy-wating 하는게 더 빠를 수 있다. 즉, mutex 를 사용하면 오히려 context switching 때문에 시간이 더 오래 걸릴 수 있다는 것이다.
semaphore
signal mechanism 을 가진, 하나 이상의 프로세스가 critical section 에 접근 가능하도록 하는 장치이다.
mutex 코드와 거의 동일해 보이는데, value 값이 0, 1 이 아닌 음수 양수로 증가하는 식으로 동작한다는 점이 다르다. 즉, mutex 는 critical section 에 오직 한개의 프로세스만 접근이 가능하지만, semaphore 를 사용하면 한개 이상의 프로세스가 접근이 가능할 것이다.
주의해야 할 점은, 두개 이상의 프로세스가 공유 자원에 동시에 접근할 수 있기 때문에 race condition 의 위험성이 다시 생긴다는 것이다. 그렇기에 사실상 semaphore 는 mutual exclusion 의 목적 보다는 신호 전달, 순서 보장, 자원의 개수 제한에 사용될 것이다.
물론, value 를 1과 0값만 사용하는 binary semaphore 의 경우 mutual exlcusion 을 보장한다. 하지만, 이는 mutex 와는 엄연히 다른 것이다.
mutex 와 binary semaphore 가 다른 것이라고?
그렇다. mutex 는 lock 을 가진 프로세스만 lock 을 해제할 수 있다. 하지만 그에 비해 semaphore 는 lock 을 취득하는 프로세스와, 이를 해제하는 프로세스가 다를 수 있다.
이러한 특징 때문에 mutex 는 priorty inheritance 라는 속성을 가진다. 기본적으로 우선순위가 높은 스레드와 낮은 스레드가 lock 을 공유하는 상황에서 낮은 스레드가 lock 을 취득한 상태라면, 우선순위가 높은 스레드는 우선순위가 높음에도 불구하고 스케쥴링될 수 없다. 이러한 문제를 해결하기 위해 mutex 에서는 우선순위가 낮은 스레드를 lock 을 기다리는 우선순위 높은 스레드와 같은 우선순위로 올려준다.
mutex 에서 priority inheritance 가 적용되지 않은 경우 위와 같이 동작한다. 우선 순위가 낮은 T(L) 이 먼저 lock 을 취득하여, T(H) 는 스케쥴링 받지 못하는 상황이다. 이 때 중간 우선순위인 T(M) 에 의하여 스케쥴링이 더욱 늦어지면 문제가 심각해질 수 있다.
priority inheritance 가 적용 되어, T(L) 의 우선순위가 순간적으로 T(H) 만큼 높아져 T(M) 보다 훨씬 빠르게 스케쥴링 된다. 성공적으로 unlock 이 호출되고, 이어서 T(H) 가 lock 을 취득하게 되어 우선순위 관련한 문제가 없어진다.
semaphore 는 위와 같은 속성이 없다. 그 이유는 누가 lock 을 해제할지, 누가 시그널을 보낼지를 알 수 없기 때문이다. 즉, 우선순위 역전 문제를 해결하기가 어렵다.
결론적으로, mutual exclusion 만 필요하다면 mutex 를, 작업 간의 실행 순서 동기화가 필요한 경우에는 semaphore 를 사용하는 것이 권장된다.
참고 자료
https://www.youtube.com/watch?v=gTkvX2Awj6g
'운영체제' 카테고리의 다른 글
Segmentation & Paging (0) | 2023.06.15 |
---|---|
Scheduling (1) | 2023.05.13 |
Virtual Memory (0) | 2023.05.03 |
IPC (Inter-Process Communication) (0) | 2023.04.24 |