이전 글(https://velog.io/@seokhwan-an/여러-프로세스는-어떤-순서로-동작할까)을 통해서는 운영체제가 여러 프로세스들이 한정된 자원인 Cpu을 이용하도록 스케줄링하는 방법에 대해서 알아보았습니다. CPU 같은 경우는 스케줄링 기법을 통해 우선순위를 통해 프로세스의 작업 순서를 정하고 CPU를 할당해주었습니다. 그러면 다른 공유 자원에 대해서는 여러 프로세스들이 접근해서 이용하려고 하면 어떻게 작동할까요? 스케줄링과 같이 여러 프로세스들이 순차적으로 동작하게 하면 되지 않을까요? 이번 글에서 알아보고자 합니다.
경쟁 조건은 여러 프로세스나 스레드가 동시에 공유 자원에 접근해 데이터를 이용할 때 접근 순서에 따라 결과가 달라지는 상황을 의미합니다. 티켓팅 사이트를 예를 들어서 간단히 살펴보겠습니다.
두 스레드가 티켓이라는 공유 자원에 접근해서 예매를 하는 경우를 보겠습니다. 저희가 기대하는 동작은 한 스레드만 티켓팅만 성공하는 것일 겁니다. 하지만 해당 그림을 보면 두 스레드 모두 성공하는 상황이 발생합니다.
이렇게 되면 존재하는 티켓 개수와 티켓 예매 데이터의 개수가 다른 데이터 정합성 문제가 발생합니다.
해당 문제의 원인은 두 스레드가 티켓이라는 자원에 동시에 접근해서 발생한 문제입니다. 그러면 하나의 스레드가 우선 예매 작업을 수행하고 완료된 후 다른 스레드가 이를 처리하면 저희가 의도했던 동작이 이루어질 것입니다. 이를 동기화
라고 합니다.
임계 영역
은 공유자원을 활용하기 위해 접근하는 공간이며 임계 영역에는 동기화 방식에 따라 하나 혹은 정해진 수 만큼의 스레드 및 프로세스가 접근 가능합니다.
프로세스 및 스레드간 동기화 작업은 상호배제를 통해 실현됩니다. 상호 배제란 하나의 공유 자원에 대해 한 번에 하나의 프로세스 및 스레드만 접근할 수 있도록 하는 메커니즘으로 lock을 이용해 구현됩니다.
상호 배제를 구현하는 방식은 스핀락, 뮤텍스, 세마포어가 있고 하나씩 알아보겠습니다. 모든 방식의 원리는 락 흭득 → 임계 영역 실행 → 락 반납 형식으로 동작합니다.
스핀락은 스레드가 락을 얻을 때까지 무한 루프를 돌며 기다리고 락을 얻은 하나의 스레드 및 프로세스만 작업을 하는 상호배제 기법입니다. 스핀락의 특징은 락을 얻기까지 여러 스레드나 프로세스가 휴면 상태로 가는 것이 아닌 무한루프를 돌며 활성화된 상태라는 특징이 있습니다. 이를 Busy Wait
이라고 합니다.
Busy Wait은 문맥 교환이 필요없기에 스레드 및 프로세스간의 문맥 교환이 자주 일어나는 곳에서는 오버헤드가 발생하지 않는다는 장점이 있습니다. 다만, 스레드나 프로세스가 활성화 상태라는 것은 cpu 자원을 활용하는 것을 의미하기에 임계영역의 작업시간이 길어져 대기하는 스레드 및 프로세스가 많아지게 되면 cpu의 자원이 낭비되는 문제가 있습니다.
뮤텍스는 단일 공유 자원에 대해서 상호 배제를 적용한 기법입니다. 공유 자원에 접근을 하면 락을 흭득하고 작업을 마무리 하면 락을 반납합니다. 공유 자원에 락을 설정하는 경우 다른 스레드 혹은 프로세스는 공유 자원에 접근이 불가능합니다. 뮤텍스의 특징은 락을 적용하는 주체와 해제하는 주체가 같다는 특징이 있습니다. 즉, lock을 반납하는 주체를 예측할 수 있습니다.
뮤텍스의 경우 락을 흭득하지 못한 프로세스나 스레드의 경우 큐에서 관리되며 앞선 스레드 및 프로세스의 작업이 마무리가 된다면 큐 중에 하나의 스레드 및 프로세스를 활성화 합니다. 이를 Non Busy Wait
이라고 합니다. (공유 자원을 흭득하지 못한 스레드 및 프로세스에 대해서 cpu 낭비를 하지 않습니다.)
뮤텍스는 한 번에 하나의 스레드 및 프로세스가 임계 영역에 접근할 수 있는 방식이라면 세마포어는 카운팅 매커니즘을 이용해 동시에 여러 스레드 및 프로세스가 자원에 접근할 수 있는 방식입니다. (정수값을 통해 공유자원의 접근 스레드 및 프로세스를 관리합니다.)
스레드나 프로세스가 공유 자원에 접근하려면 wait을 통해 lock을 흭득합니다. lock을 흭득한 스레드나 프로세스가 생기면 정수값을 감소시키고 정수값이 0이되면 더이상 스레드 및 프로세스를 접근하지 못하게 막습니다. 작업을 마무리 한 스레드 및 프로세스가 signal을 통해 lock을 반납한 후에 대기중인 스레드 및 프로세스를 활성화해 lock을 흭득하는 방식으로 동작합니다.
접근할 수 있는 스레드 및 프로세스가 1개인 세마포어를 binary 세마포어라고 하고 접근할 수 있는 스레드 및 프로세스가 1개보다 많은 경우를 Counting 세마포어라고 합니다.
특징 | 스핀락 (Spinlock) | 뮤텍스 (Mutex) | 세마포어 (Semaphore) |
---|---|---|---|
기본 개념 | 반복적으로 잠금 상태를 확인하며 잠금 해제를 기다림 | 단일 소유자만 접근 가능한 공유 자원 잠금 | 카운터를 기반으로 다수의 스레드가 접근할 수 있는 공유 자원 제어 |
잠금 대상 | 1개의 스레드 | 1개의 스레드 | N개의 스레드 (동시 접근 제한 가능) |
잠금 획득 방식 | 루프를 통해 잠금 상태를 확인하며 획득 | 잠금 상태 변경 (Lock/Unlock 메서드 호출) | 카운터 감소 (0이면 대기) |
잠금 해제 방식 | 잠금 상태를 확인하여 직접 해제 | 소유자가 직접 해제 | 카운터 증가 |
CPU 효율성 | Busy Wait으로 Cpu 낭비 발생 O | Non Busy Wait으로 대기의 과정에서 CPU 낭비 발생 X | Non Busy Wait으로 대기의 과정에서 CPU 낭비 발생 X |
컨텍스트 스위칭 | 컨텍스트 스위칭 없음 (대기 시간 길 경우 비효율적) | 대기 중 컨텍스트 스위칭 발생 | 대기 중 컨텍스트 스위칭 발생 |