OS Race Condition
Race Condition (경쟁 상태)
Race Condition
은 다중 스레드 또는 다중 프로세스 환경에서 두 개 이상의 작업이 동시에 수행되면서 공유 자원에 대한 비정상적이고 예기치 않은 동작을 유발하는 상황입니다. 동시성이 필요한 상황에서 올바른 동기화가 이루어지지 않았을 때 발생하는 문제로, 특정 코드나 데이터를 올바른 순서로 처리하지 못해 의도하지 않은 결과를 초래할 수 있습니다.
Race Condition의 발생 조건
Race Condition은 다음 두 가지 조건이 충족될 때 발생합니다:
- 병렬 처리: 여러 스레드나 프로세스가 동시에 실행되고 있는 환경.
- 공유 자원 접근: 여러 작업이 동일한 공유 자원(예: 변수, 메모리)에 접근 및 수정하려 할 때.
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(입금)*이
balance
를 읽어 1000이라는 값을 얻습니다. - 스레드 2(출금)*도 동시에
balance
를 읽어 1000이라는 값을 얻습니다. - 스레드 1은 1000에 100을 더해 1100을
balance
에 저장합니다. - 스레드 2는 1000에서 100을 빼서 900을
balance
에 저장합니다. - 최종 잔액은 900으로 저장됩니다. 그러나 이 값은 잘못된 결과입니다. (입금과 출금이 정확히 반영되지 않음).
Race Condition 해결 방법
Race Condition을 해결하려면, 동기화 기법을 사용해 공유 자원에 대한 접근을 제어해야 합니다. 주로 사용되는 동기화 기법은 다음과 같습니다:
- 뮤텍스(Mutex, Mutual Exclusion)
- 세마포어(Semaphore)
- 모니터(Monitor)
- 조건 변수(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 방지 전략
- 동기화 기법 사용: 뮤텍스, 세마포어, 조건 변수 등을 사용해 자원에 대한 접근을 제어합니다.
- 임계 구역 설정: 임계 구역을 정의하여 특정 코드 블록에서 동시 실행을 제한합니다.
- 원자적 연산: 일부 연산은 원자적으로 처리되어 Race Condition을 방지할 수 있습니다. 예를 들어, atomic operations를 사용하면, 다른 스레드의 개입 없이 단일 명령으로 처리됩니다.
- 프로그램 설계 개선: 프로그램을 설계할 때 동시성 문제를 고려하여 Race Condition이 발생하지 않도록 설계합니다.
결론
Race Condition은 동시성 프로그래밍에서 중요한 문제로, 프로그램이 의도한 대로 동작하지 않게 만들 수 있습니다. 이를 방지하기 위해서는 올바른 동기화가 필수적이며, 뮤텍스나 세마포어 같은 기법을 통해 자원에 대한 동시 접근을 제어해야 합니다.