Interview/OS

OS Race Condition

김 정출 2024. 9. 27. 18:06

Race Condition (경쟁 상태)

Race Condition은 다중 스레드 또는 다중 프로세스 환경에서 두 개 이상의 작업이 동시에 수행되면서 공유 자원에 대한 비정상적이고 예기치 않은 동작을 유발하는 상황입니다. 동시성이 필요한 상황에서 올바른 동기화가 이루어지지 않았을 때 발생하는 문제로, 특정 코드나 데이터를 올바른 순서로 처리하지 못해 의도하지 않은 결과를 초래할 수 있습니다.


Race Condition의 발생 조건

Race Condition은 다음 두 가지 조건이 충족될 때 발생합니다:

  1. 병렬 처리: 여러 스레드나 프로세스가 동시에 실행되고 있는 환경.
  2. 공유 자원 접근: 여러 작업이 동일한 공유 자원(예: 변수, 메모리)에 접근 및 수정하려 할 때.

Race Condition 예시

다음은 Race Condition이 발생할 수 있는 단순한 예제입니다.

예제 1: 은행 계좌의 잔액 증가와 감소

두 스레드가 하나의 은행 계좌에 대해 동시에 작업을 수행한다고 가정합니다. 한 스레드는 돈을 입금하고, 다른 스레드는 돈을 출금합니다.

#include <stdio.h>
#include <pthread.h>

int balance = 1000; // 계좌 잔액

void* deposit(void* arg) {
    int amount = *((int*)arg);
    balance += amount; // 잔액에 입금
    return NULL;
}

void* withdraw(void* arg) {
    int amount = *((int*)arg);
    balance -= amount; // 잔액에서 출금
    return NULL;
}

int main() {
    pthread_t t1, t2;
    int amount = 100;

    // 스레드 1: 입금
    pthread_create(&t1, NULL, deposit, &amount);

    // 스레드 2: 출금
    pthread_create(&t2, NULL, withdraw, &amount);

    // 스레드 종료 대기
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    printf("최종 잔액: %d\n", balance);
    return 0;
}

이 프로그램은 두 스레드가 동시에 잔액을 수정합니다. 하지만 Race Condition 때문에 잔액이 의도하지 않은 값으로 변경될 수 있습니다.

  • 문제점: balance 변수는 하나의 공유 자원이지만, 입금과 출금 작업이 동시에 일어나는 동안 서로 간섭할 수 있습니다. 스레드 1이 값을 읽고 수정하기 전에 스레드 2가 값을 변경할 수 있습니다.

Race Condition 발생 시나리오

  1. 스레드 1(입금)*balance를 읽어 1000이라는 값을 얻습니다.
  2. 스레드 2(출금)*도 동시에 balance를 읽어 1000이라는 값을 얻습니다.
  3. 스레드 1은 1000에 100을 더해 1100balance에 저장합니다.
  4. 스레드 2는 1000에서 100을 빼서 900balance에 저장합니다.
  5. 최종 잔액은 900으로 저장됩니다. 그러나 이 값은 잘못된 결과입니다. (입금과 출금이 정확히 반영되지 않음).

Race Condition 해결 방법

Race Condition을 해결하려면, 동기화 기법을 사용해 공유 자원에 대한 접근을 제어해야 합니다. 주로 사용되는 동기화 기법은 다음과 같습니다:

  1. 뮤텍스(Mutex, Mutual Exclusion)
  2. 세마포어(Semaphore)
  3. 모니터(Monitor)
  4. 조건 변수(Condition Variables)

뮤텍스(Mutex)를 사용한 동기화 예시

다음은 Mutex를 사용해 앞서 설명한 은행 계좌 예시에서 Race Condition을 해결하는 방법입니다.

#include <stdio.h>
#include <pthread.h>

int balance = 1000; // 계좌 잔액

pthread_mutex_t mutex; // 뮤텍스 선언

void* deposit(void* arg) {
    int amount = *((int*)arg);

    pthread_mutex_lock(&mutex); // 뮤텍스 잠금
    balance += amount;          // 잔액에 입금
    pthread_mutex_unlock(&mutex); // 뮤텍스 해제

    return NULL;
}

void* withdraw(void* arg) {
    int amount = *((int*)arg);

    pthread_mutex_lock(&mutex); // 뮤텍스 잠금
    balance -= amount;          // 잔액에서 출금
    pthread_mutex_unlock(&mutex); // 뮤텍스 해제

    return NULL;
}

int main() {
    pthread_t t1, t2;
    int amount = 100;

    pthread_mutex_init(&mutex, NULL); // 뮤텍스 초기화

    // 스레드 1: 입금
    pthread_create(&t1, NULL, deposit, &amount);

    // 스레드 2: 출금
    pthread_create(&t2, NULL, withdraw, &amount);

    // 스레드 종료 대기
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    printf("최종 잔액: %d\n", balance);

    pthread_mutex_destroy(&mutex); // 뮤텍스 삭제
    return 0;
}

설명:

  • pthread_mutex_lock(&mutex): 공유 자원에 접근하기 전에 뮤텍스를 잠급니다. 이를 통해 다른 스레드가 자원에 동시에 접근하지 못하게 합니다.
  • pthread_mutex_unlock(&mutex): 자원 사용이 끝나면 뮤텍스를 해제하여 다른 스레드가 자원에 접근할 수 있게 합니다.

이 방식으로 Race Condition을 방지하고, 여러 스레드가 동시에 공유 자원에 접근할 때 동기화를 보장할 수 있습니다.


Race Condition의 영향

  • 데이터 불일치: 여러 스레드 또는 프로세스가 공유 자원에 동시에 접근하여 데이터가 일관성을 잃을 수 있습니다.
  • 프로그램 비정상 종료: 잘못된 동기화로 인해 충돌, 데드락 또는 무한 대기 상태가 발생할 수 있습니다.
  • 보안 문제: 일부 Race Condition은 보안 취약점으로 악용될 수 있습니다. 예를 들어, 특정 시스템 자원에 대한 접근이 보안적으로 동기화되지 않으면 권한 상승 등의 문제가 발생할 수 있습니다.

Race Condition 방지 전략

  1. 동기화 기법 사용: 뮤텍스, 세마포어, 조건 변수 등을 사용해 자원에 대한 접근을 제어합니다.
  2. 임계 구역 설정: 임계 구역을 정의하여 특정 코드 블록에서 동시 실행을 제한합니다.
  3. 원자적 연산: 일부 연산은 원자적으로 처리되어 Race Condition을 방지할 수 있습니다. 예를 들어, atomic operations를 사용하면, 다른 스레드의 개입 없이 단일 명령으로 처리됩니다.
  4. 프로그램 설계 개선: 프로그램을 설계할 때 동시성 문제를 고려하여 Race Condition이 발생하지 않도록 설계합니다.

결론

Race Condition동시성 프로그래밍에서 중요한 문제로, 프로그램이 의도한 대로 동작하지 않게 만들 수 있습니다. 이를 방지하기 위해서는 올바른 동기화가 필수적이며, 뮤텍스세마포어 같은 기법을 통해 자원에 대한 동시 접근을 제어해야 합니다.