포스트

[OS] 경쟁 상태 (Race Condition) & 세마포어 / 뮤텍스

[OS] 경쟁 상태 (Race Condition) & 세마포어 / 뮤텍스

경쟁 상태 (Race Condition)

📌 개념

공유 자원에 대해 여러 프로세스가 동시에 접근할 때, 실행 순서에 따라 결과값이 달라질 수 있는 상태

동시 접근 시 자료의 일관성(Consistency)이 깨질 수 있으며, 이는 예측 불가능한 버그로 이어진다.

간단한 예시:

1
2
3
4
5
6
7
전역 변수 count = 0

Thread A: count = count + 1  (읽기 → 연산 → 쓰기)
Thread B: count = count + 1  (읽기 → 연산 → 쓰기)

기대 결과: count = 2
실제 결과: count = 1  ← 두 스레드가 동시에 읽어 덮어씀

이처럼 연산이 원자적(Atomic)으로 수행되지 않을 때 경쟁 상태가 발생한다.


⚠️ Race Condition이 발생하는 경우

1. 커널 작업 수행 중 인터럽트 발생

  • 문제: 커널 모드에서 데이터를 로드하여 작업 중, 인터럽트가 발생해 같은 데이터를 또 조작하는 경우
  • 해결: 커널 모드에서 작업하는 동안 인터럽트를 disable 시켜 CPU 제어권을 빼앗기지 않도록 함
1
2
3
4
[커널 작업 중]
  1. count 값 읽음 (count = 5)
  ← 인터럽트 발생! 인터럽트 핸들러가 count를 6으로 변경
  2. 원래 작업이 5 기준으로 연산 후 저장 → 인터럽트 결과 덮어씌워짐

2. System Call 중 Context Switching 발생

  • 문제: 프로세스 1이 커널 모드에서 데이터를 조작하는 도중, 타임아웃으로 CPU 제어권이 프로세스 2로 넘어가 같은 데이터를 조작하는 경우 → 프로세스 2의 작업이 반영되지 않음
  • 해결: 커널 모드에서 작업 중일 때는 타임아웃이 발생해도 CPU 제어권을 넘기지 않도록 함
1
2
3
Process 1: read(count=5) → [time out] → CPU 넘어감
Process 2: read(count=5) → count=6 저장 → CPU 반환
Process 1: count=6 저장 → Process 2의 결과 덮어씌워짐

3. 멀티 프로세서 환경에서 공유 메모리 접근

  • 문제: 멀티 프로세서 환경에서 2개의 CPU가 동시에 커널 내부의 공유 데이터에 접근하여 조작하는 경우
  • 해결: 공유 데이터에 접근할 때마다 lock / unlock 방식으로 상호 배제를 보장
1
2
CPU 1: lock → 공유 데이터 조작 → unlock
CPU 2: lock 시도 → CPU 1의 unlock 대기 → lock 획득 후 조작

🔐 임계 구역 (Critical Section)

Race Condition을 이해하려면 임계 구역 개념이 필수다.

여러 프로세스가 공유하는 데이터를 접근 및 수정하는 코드 영역

임계 구역 문제를 해결하려면 아래 3가지 조건을 모두 만족해야 한다.

조건설명
상호 배제 (Mutual Exclusion)한 프로세스가 임계 구역을 실행 중이면 다른 프로세스는 진입 불가
진행 (Progress)임계 구역에 프로세스가 없을 때 진입 요청한 프로세스는 반드시 진입 가능
한정 대기 (Bounded Waiting)임계 구역 진입 요청 후 무한정 기다리지 않도록 대기 횟수에 한계가 있어야 함

임계 구역 코드 구조

1
2
3
4
5
6
do {
    [entry section]   // 진입 허가 요청
        critical section  // 임계 구역 (공유 자원 접근)
    [exit section]    // 임계 구역 종료 알림
        remainder section // 나머지 코드
} while (true);

🛠️ 해결 방법 — 세마포어 & 뮤텍스


세마포어 (Semaphore)

멀티프로그래밍 환경에서 공유 자원에 대한 접근을 제한하는 방법으로, 정수 카운터 S를 이용해 동시 접근 가능한 프로세스 수를 제어한다.

P, V 연산

연산시점동작
P(S)임계 구역 진입 전S 값을 확인하고 1 감소. S = 0이면 대기
V(S)임계 구역 나올 때S 값을 1 증가. 대기 프로세스 깨움
1
2
3
4
5
6
7
8
9
10
procedure P(S)      -- 최초 S값은 1
    while S = 0 do wait   -- S가 0이면 1이 될 때까지 대기
    S := S - 1            -- S를 0으로 만들어 다른 프로세스 진입 차단
end P

--- 임계 구역 ---

procedure V(S)      -- 현재 S = 0
    S := S + 1            -- S를 1로 복원하여 해제
end V

동작 예시 (프로세스 A, B, 초기 S = 1):

  1. A가 P(S) 실행 → S = 0, 임계 구역 진입
  2. B가 P(S) 실행 → S = 0이므로 대기
  3. A가 임계 구역 수행 후 V(S) 실행 → S = 1
  4. B가 대기 해제되어 임계 구역 진입

P와 V 수행 중에는 인터럽트를 당하지 않아 상호 배제가 보장된다.

세마포어 종류

구분설명
이진 세마포어 (Binary)S = 0 또는 1만 가짐. 뮤텍스와 유사하게 동작
계수 세마포어 (Counting)S ≥ 0의 정수. 여러 프로세스의 동시 접근 허용

뮤텍스 (Mutex)

Mutual Exclusion의 약자

임계 구역을 가진 스레드들의 실행 시간이 겹치지 않고 단독으로 실행되도록 보장하는 기술. lock / unlock 두 상태만 가지므로 이진 세마포어라고도 불린다.

  • lock : 임계 구역 진입 권한 획득. 다른 스레드가 수행 중이면 종료될 때까지 대기
  • unlock : 임계 구역 사용 완료 알림. 대기 중인 스레드가 진입할 수 있게 됨
1
2
3
mutex.lock()
// --- 임계 구역 ---
mutex.unlock()

뮤텍스 구현 알고리즘

1. 데커(Dekker) 알고리즘

flag와 turn 변수를 통해 임계 구역에 진입할 프로세스를 결정하는 방식. 두 변수를 모두 사용한다는 점에서 복잡하지만, 최초의 소프트웨어적 상호 배제 해결법이다.

  • flag[i] : 프로세스 i가 임계 구역 진입을 원하는지 나타냄
  • turn : 누가 임계 구역에 들어갈 차례인지 나타냄
1
2
3
4
5
6
7
8
9
10
11
12
13
while(true) {
    flag[i] = true;          // 프로세스 i가 진입 시도
    while(flag[j]) {         // 프로세스 j가 임계 구역에 있는지 확인
        if(turn == j) {
            flag[i] = false; // 진입 취소
            while(turn == j); // turn이 바뀔 때까지 대기
            flag[i] = true;  // 다시 진입 시도
        }
    }
    // --- 임계 구역 ---
    turn = j;                // turn을 상대에게 양보
    flag[i] = false;         // 사용 완료
}

2. 피터슨(Peterson) 알고리즘

데커 알고리즘을 개선한 방식. 상대방에게 먼저 기회를 양보(turn = j) 함으로써 코드가 훨씬 단순해진다.

1
2
3
4
5
6
7
while(true) {
    flag[i] = true;              // 프로세스 i가 진입 시도
    turn = j;                    // 상대에게 먼저 기회 양보
    while(flag[j] && turn == j); // 상대가 진입 시도 중이면 대기
    // --- 임계 구역 ---
    flag[i] = false;             // 사용 완료
}

데커 vs 피터슨 비교:

구분데커피터슨
양보 방식상대 turn이 끝날 때까지 flag 내리고 대기turn을 상대에게 직접 넘김
코드 복잡도복잡단순

3. 제과점(Bakery) 알고리즘

데커/피터슨이 2개 프로세스만 처리 가능한 것과 달리, n개의 프로세스를 처리할 수 있는 알고리즘. 빵집에서 번호표를 뽑아 순서를 기다리는 것에서 유래했으며, 가장 작은 번호표를 가진 프로세스가 임계 구역에 진입한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
while(true) {
    isReady[i] = true;                              // 번호표 받을 준비
    number[i] = max(number[0..n-1]) + 1;           // 현재 최대 번호 + 1 배정
    isReady[i] = false;                             // 번호표 수령 완료

    for(j = 0; j < n; j++) {
        while(isReady[j]);                          // j가 번호표 받을 때까지 대기
        while(number[j] && number[j] < number[i] && j < i);
        // j의 번호가 존재하고, j 번호 < i 번호인 동안 대기
    }
    // --- 임계 구역 ---
    number[i] = 0;                                  // 임계 구역 사용 종료
}

세마포어 vs 뮤텍스 비교

구분세마포어뮤텍스
접근 허용 수하나 이상 (Counting) 또는 하나 (Binary)오직 하나
소유권소유 개념 없음 (다른 스레드가 signal 가능)Lock을 획득한 스레드만 해제 가능
사용 범위프로세스 간 동기화 가능주로 스레드 간 동기화
종류이진 / 계수 세마포어단일 (이진 세마포어와 유사)
교착 상태잘못 사용 시 발생 가능잘못 사용 시 발생 가능

모니터 (Monitor) — 보충

뮤텍스와 세마포어의 단점(직접 lock/unlock 관리 → 실수 가능)을 보완한 고수준 동기화 도구. 임계 구역과 동기화 로직을 내부에 캡슐화하여 프로그래머가 명시적으로 제어하지 않아도 된다.

Java의 synchronized 키워드가 대표적인 예시다.

1
2
3
synchronized void increment() {
  count++;  // JVM이 자동으로 상호 배제 보장
}

🔄 Race Condition vs DeadLock 비교

구분Race ConditionDeadLock
원인동시에 공유 자원 접근서로 자원을 점유한 채 대기
결과데이터 불일치, 예측 불가능한 동작프로세스 무한 대기 (시스템 중단)
핵심 문제실행 순서 의존성자원 순환 대기
해결동기화 (뮤텍스, 세마포어 등)예방, 회피, 탐지, 회복

✅ 요약 정리

항목내용
Race Condition공유 자원에 여러 프로세스가 동시 접근 시 결과가 달라지는 현상
발생 상황커널 인터럽트 / System Call 중 Context Switching / 멀티 프로세서 공유 메모리 접근
임계 구역공유 데이터를 접근하는 코드 영역. 상호 배제 / 진행 / 한정 대기 조건 필요
세마포어P/V 연산으로 접근 수를 제어. 소유권 없음
뮤텍스lock/unlock으로 단독 실행 보장. lock 획득자만 해제 가능
뮤텍스 알고리즘데커(복잡, 최초) → 피터슨(단순, 2개) → 제과점(n개 프로세스)

💬 면접 예상 질문

  1. Race Condition(경쟁 상태)란 무엇이고, 언제 발생하나요?
  2. 임계 구역(Critical Section) 문제를 해결하기 위한 3가지 조건을 설명하세요.
  3. 세마포어의 P, V 연산이란 무엇인가요?
  4. 뮤텍스와 세마포어의 차이점은 무엇인가요?
  5. 데커, 피터슨, 제과점 알고리즘의 차이를 설명하세요.
  6. Race Condition과 DeadLock의 차이를 설명하세요.

참고:

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.