본문 바로가기
Computer Science/운영체제

[OS] Race Condition과 동기화란? (Race Condition 발생 상황, 임계영역, 뮤텍스, 세마포어, 모니터)

by 그적 2024. 1. 20.

목차

  • Race Condition과 동기화란?
  • Race Condition이 발생할 수 있는 상황
  • 임계영역 (Critical Section)
  • Race Condition 해결 방법 

           (1) 뮤텍스 (Mutex)

           (2) 세마포어 (Semaphore)

           (3) 모니터 (Monitor)

 

  • 뮤텍스, 세마포어, 모니터 비교와 결론

1. 경쟁 상태(Race Condition)와 동기화(Synchronized)란?

여러 개의 프로세스나 스레드가 하나의 공유 자원에 접근할 때, 접근 순서에 따라 실행 결과가 달라지는 상황을 발생할 수 있다. 결국 공유된 자원의 일관성을 보장할 수 없는 것을 의미하며, 이를 경쟁 상태라고 말한다.

 

경쟁 상태를 막기 위해 프로세스나 스레드가 공유하는 자원들의 일관성을 보장하는 것이 동기화이다. 따라서 일관성이 깨질 수 있는 인터럽트, 시스템 콜 혹은 멀티 프로세스 상황에서 동기화 문제를 해결하는 기법들이 필요한 것이다.

 


2. 경쟁 상태가 발생할 수 있는 상황

① 커널 모드 수행 중에 인터럽트(Interrupt) 발생 시

 : 커널 모드로 수행 중이라는 의미는 CPU가 메모리, 디스크 등 자원을 사용하고 있다는 의미이다. 이때 인터럽트가 발생하면 수행 중이던 명령을 다 끝마치지 못하게 되면서, 미처 남은 명령을 다시 수행하기 전(= 인터럽트가 끝날 때)까지 작업하고 있던 자원의 일관성을 보장할 수 없게 된다.

 

아래 사진은 Load > Increase > Store 명령을 끝난 후에는 count = 1 값이 저장되어야 하는데, 도중에 인터럽트가 발생해 count--; 명령이 추가로 수행되면서 결과적으로 count = 0 값이 저장된다.

 

② 커널 모드 수행 중에 시스템 콜 발생 시

 : 프로세스는 자원을 공유하지 않는다. 하지만 커널 영역의 코드와 데이터를 공유되어 사용되는데, 실행 중에 시스템 콜(= 컨텍스트 스위칭)이 발생한다면 코드를 끝마치지 못한 채로 제어권이 넘어가게 된다. 따라서 커널 모드 수행 중에는 컨텍스트 스위칭이 발생하지 않고, 유저 모드로 돌아갈 때 CPU 제어권을 넘길 수 있게 해야 한다.

 

아래 사진은 첫 번째 프로세스 A에서 커널 모드에서 count++; 명령이 실행되어 총 두 번의 카운팅이 되어야 했다. 하지만 커널 모드 중간에 프로세스 B에게 CPU가 선점되어 count++; 라는 명령을 실행하지 못했다.

 

③ 멀티 프로세스

멀티 프로세스 환경에서 경쟁 상황이 발생할 수 있다. (흔치는 않음) 멀티 프로세스는 CPU가 여러 개로 동작하기 때문에 어떤 CPU가 마지막 동작을 했는지에 따라 값이 달라질 수 있는 것이다.

 


3. 임계영역

다른 프로세스와 공유하는 데이터에 접근하고, 그 데이터를 갱신할 수 있는 코드 영역을 임계영역이라고 한다. 하나의 프로세스가 임계영역에 들어가 있을 경우, 다른 프로세스가 임계영역에 들어가는 것을 막아야 동기화 상태를 유지할 수 있다.

 

임계영역 사용 시, 문제가 발생하지 않게 하기 위해서 다음 조건을 충족해야 한다.

  • Mutual Exclusion (상호 배제) : 하나의 프로세스가 이미 임계영역에 존재한다면, 다른 프로세스는 진입할 수 없다.
  • Progress (진행) : 임계영역에 작업 중인 프로세스가 없다면, 진입하고자 하는 프로세스가 생기면 진입할 수 있다.
  • Bounded Waiting (한정 대기) : 임계영역에 들어가기 위한 요청이 허용될 때까지 진입 횟수에 제한이 있어야 한다.

 


** 잠깐 알고가기 **

프로세스는 여러 개의 스레드 집합으로 구성되어 있다. 그러므로 프로세스 동기화를 지원한다는 것은, 스레드 동기화를 지원하는 것을 내포하고 있다. 하지만 스레드 동기화를 지원한다는 것은 프로세스 동기화를 지원하는 것이 아니라는 것을 명심해야 한다.

 

뮤텍스와 세마포어는 프로세스 동기화를 지원하고, 모니터는 스레드 동기화를 지원한다. 모니터는 객체 지향 프로그래밍 언어로 작성되어 동작하기 때문에, 이미 하나의 프로세스가 생성된 상태에서 스레드 간의 공유 영역을 관리한다는 의미로 볼 수 있다.

 

모니터는 프로그램 코드가 실행되면서 객체가 락을 가지거나 혹은 클래스가 락을 가지는 것에 따라 각각 다른 동기화 방법을 제공한다. static이 아닌 변수나 메서드에 synchronized 키워드가 붙으면 객체를 생성해 이러한 변수나 메서드를 사용할 수 있는데, 이때 락이 걸리는 주체는 객체가 된다. 하지만 static 변수나 메서드에 synchronized 키워드가 붙으면 락이 걸리는 주체는 클래스가 된다. 만약 락을 획득한 객체와 클래스가 동시에 실행이 될 경우, 서로 가진 락의 종류가 다르기 때문에 둘 사이에는 동기화가 발생하지 않는다.

 

결론은 모니터의 경우에는 이러한 코드 단에서의 동기화를 제공하기 때문에 프로세스 동기화가 아닌, 스레드 동기화를 지원한다고 봐야한다.

 

(참고 블로그 : https://joonfluence.tistory.com/776)

 


4. Race Condition 해결 방법 : (1) Mutex, 뮤텍스

** acquire lock : 진입 시도(busy waiting) 및 Lock 획득

** release lock : Lock 해제

 

(특징)

  • 공유 자원에 대한 여러 프로세스의 접근을 막아 단 하나의 프로세스만 동작할 수 있도록 한다.
  • 임계영역 진입 시 Lock을 걸고, 빠져나올 때 Lock을 해제한다. 
  • 여러 개의 프로세스 동기화를 지원한다.
  • 하나의 프로세스가 임계영역에 존재할 때 다른 프로세스는 진입할 수 없는 상호 배제를 위한 알고리즘이다.
  • Lock에 걸려 임계영역에 들어가진 못한 프로세스는 대기모드에 들어가기 때문에 Context Switching이 발생한다. 따라서 Windows와 같은 멀티 프로세스 환경에서 종종 사용되는 기법이다.

(장점)

  • 구현이 간단하고 빠르게 동작한다.
  • 임계영역 코드가 짧을 경우에는 Lock에 걸린 프로세스가 Context Switching 되는 비용보다 Lock 해제를 기다리는 비용이 더 작을 수도 있다. 이를 위해 뮤텍스의 Busy Waiting을 통해 불필요한 Context Switching에서 발생하는 오버헤드를 줄이는 것이다.

(단점)

  • Lock이 풀린 것을 계속 확인하면서 대기하기 때문에, Busy Waiting 문제가 존재하고 CPU가 낭비된다.
  • 공유 자원을 사용하는 프로세스의 처리 속도가 느리거나 임계영역 코드가 길면, CPU 부담이 커진다.

 


5. Race Condition 해결 방법 : (2) Semaphore, 세마포어

typedef struct {
    int value; // 세마포어 S 변수 값
    struct process *list; // 세마포어 대기 큐
} Semaphore;

// 공유 데이터 요청
wait(Semaphore S) {
    S->value--;
    
    // 사용할 수 있는 공유 자원이 없으면 block -> waiting queue로 보내짐.
    if (S->value < 0) {
        block();
    }
}

// 공유 데이터 반환
signal(Semaphore S) {
    S->value++;
    
    // 사용할 수 있는 공유 자원이 없으므로 대기하던 프로세스 wakeUP -> ready queue로 보내짐
    if (S->value <= 0) { 
        wakeUp();
    }
}

** Block된 프로세스(PCB)는 Waiting Queue에 들어간다.

** WakeUp된 프로세스(PCB)는 Ready Queue에 들어간다.

 

(특징)

  • 공유 자원에 접근할 수 있는 프로세스의 수를 제어함으로써, 설정한 개수만큼 프로세스가 임계영역을 사용할 수 있다.
  • 여러 개의 프로세스 동기화를 지원한다.
  • 세마포어는 뮤텍스로 사용될 수 있지만, 뮤텍스는 세마포어로 사용할 수 없다.
  • 세마포어 종류로는 Binary Semaphore와 Counting Semaphore가 있다.

(장점)

  • Signaling 매커니즘 혹은 Block & WakeUp 방식을 통해 Busy Waiting 단점을 해결한다.
  • 여러 개의 프로세스가 공유 자원을 사용할 수 있다.

(단점)

  • 임계영역 코드가 짧을 경우, 프로세스 상태를 변화시키는 Block & WakeUp 방식이 오버헤드가 더 커질 수 있다.
  • 두 개 이상의 프로세스가 서로의 작업이 끝나기를 기다리는 데드락이 발생할 수 있다.
  • 대기 큐에 들어있는 프로세스가 우선순위가 낮아 자원을 계속해서 할당받지 못하는 기아 현상이 발생할 수 있다.

 


6. Race Condition 해결 방법 : (6) Monitor, 모니터

(특징)

  • 내부 프로시저를 통해 하나의 스레드만 임계영역에 접근을 할 수 있도록 한다.
  • 하나의 프로세스 내 여러 개의 스레드의 동기화를 지원한다.
  • synchronized 키워드와 같이 객체 지향 프로그래밍 언어에서 라이브러리 형태로 지원하기 때문에, 유저 모드/하이 레벨/고급 수준 언어 동기화 기법이라고도 한다.

(장점)

  • 유저 모드에서 코드 형태로 지원되기 때문에, 빠르고 가볍다.
  • 뮤텍스 Lock이나 세마포어 카운팅 변수처럼 값을 직접 제어하기보다 캡슐화된 함수를 통해 편하게 동기화할 수 있다.

 


7. 뮤텍스, 세마포어, 모니터 비교

[ 뮤텍스와 세마포어 ] 

  뮤텍스 세마포어
유저모드
커널모드
커널 모드에서 동작
동기화 범위 프로세스 동기화 지원
공유 자원
접근 개수
하나의 프로세스만 임계영역에 진입 가능 여러 개의 프로세스 임계영역에 진입 가능
자원 해제 Lock을 건 프로세스만 Lock 해제 가능 Lock을 건 프로세스가 아니어도 Lock 해제 가능
대기 방식 Busy Waiting 방식 Block & WakeUp 방식
임계영역 코드짧을 때 임계영역 코드가 짧을 때 사용하기 좋음 임계영역 코드가 짧을 때
Context Switching에 발생하는 비용이 더 클 수도 있음
임계영역 코드
길 때
임계영역 코드가 길 때
Busy Waiting으로 인해 CPU가 낭비됨
임계영역 코드가 길 때 사용하기 좋음
  멀티 프로세스 환경에서 종종 사용됨 -
  - 데드락이나 기아 문제가 발생할 수 있음

 

[ 세마포어와 모니터 ]

  세마포어 모니터
유저모드
커널모드
커널 모드에서 동작 유저 모드에서 동작
동기화 범위 프로세스 동기화 지원 스레드 동기화 지원
공유 자원
접근 개수
여러 개의 프로세스만 임계영역에 진입 가능 하나의 프로세스 임계영역에 진입 가능
구현 세마포어 변수를 통해 컨트롤 synchronzed 키워드를 통해 추상화된 함수로 컨트롤
  데드락이나 기아 문제가 발생할 수 있음 -
  - 빠르고 가벼움

 

[ 뮤텍스와 모니터 ]

  뮤텍스 모니터
공유 자원
접근 개수
하나의 프로세스만 임계영역에 진입 가능
유저모드
커널모드
커널 모드에서 동작 유저 모드에서 동작
동기화 범위 프로세스 동기화 지원 스레드 동기화 지원
구현 세마포어 변수를 통해 컨트롤 synchronzed 키워드를 통해 추상화된 함수로 컨트롤
장점 임계영역 코드가 짧을 때 사용하기 좋음 빠르고 가벼움
단점 Busy Waiting 문제가 발생함 -

 

 

(핵심과 결론)

뮤텍스는 윈도우, 리눅스 등 멀티 프로세스 환경에서 기본으로 제공되고, 하나의 프로세스가 공유자원을 이용할 수 있음.

 

세마포어는 여러 개의 프로세스가 공유자원을 이용할 수 있으며, 뮤텍스의 Busy Waiting 문제를 개선했음.

 

모니터는 객체 지향 언어에서 라이브러리 형태로 코드로 제공되기 때문에 빠르고 편리하며, 코드 단에서 객체 간 혹은 클래스 간의 동기화가 필요할 때 사용함.

댓글