2025. 2. 26. 12:24ㆍ정보기술/운영체제 (OS)
📖 1-2. 임계 영역(Critical Section)과 해결 방법
멀티스레딩 환경에서 여러 개의 스레드가 동시에 실행될 때 공유 자원(Shared Resource)에 동시에 접근하면 데이터 손상(Data Corruption) 또는 예상치 못한 동작이 발생할 수 있습니다.
이러한 문제가 발생하는 코드 영역을 임계 영역(Critical Section) 이라고 하며, 이를 해결하기 위해 동기화 기법(Synchronization Techniques) 이 필요합니다.
✅ 임계 영역(Critical Section)이란?
🔹 정의
임계 영역(Critical Section)이란 여러 개의 스레드가 동시에 실행될 경우, 충돌이나 오류가 발생할 가능성이 있는 코드 영역을 의미합니다.
예제: 임계 영역이 필요한 상황
- 데이터베이스 업데이트: 두 개의 스레드가 동시에 같은 데이터베이스 레코드를 수정하면 데이터가 손실될 수 있음.
- 파일 쓰기(File Writing): 여러 개의 프로세스가 동시에 같은 파일을 수정하면 데이터가 꼬일 수 있음.
- 네트워크 요청(Network Request): 동일한 리소스를 다수의 클라이언트가 접근하면 충돌이 발생할 수 있음.
✅ 임계 영역 문제 예제 (C 코드)
동기화 없이 balance 값을 수정하는 코드에서 경쟁 상태(Race Condition) 가 발생할 수 있습니다.
#include <stdio.h>
#include <pthread.h>
int balance = 1000; // 공유 자원 (임계 영역)
void* withdraw(void* arg) {
int temp = balance; // 현재 잔액 읽기
temp -= 500; // 인출 (임계 영역)
balance = temp; // 수정된 값 저장
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, withdraw, NULL);
pthread_create(&t2, NULL, withdraw, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("최종 잔액: %d\n", balance); // 500이 아닐 수도 있음!
return 0;
}
🔴 실행 결과 예측 불가
최종 잔액: 500
최종 잔액: 1000
최종 잔액: 0 <-- 데이터 손상 발생 가능!
✅ 원인:
- Thread 1과 Thread 2가 동시에 balance -= 500 연산을 수행하면서 중간 연산 결과가 덮어씌워지는 문제 발생
- 이로 인해 잔액이 0이 될 수도 있고, 500이 유지되지 않을 수도 있음!
- 해결 방법: 임계 영역을 보호해야 함.
✅ 임계 영역 해결 방법
임계 영역에서 발생하는 문제를 해결하는 주요 방법은 다음과 같습니다.
해결 방법 | 설명 |
1. 락(Lock) 사용 | 한 번에 하나의 스레드만 임계 영역에 접근하도록 제한 |
2. 스레드 간 순서 조정 (Condition Variable 사용) | 특정 조건이 충족될 때만 스레드가 실행되도록 설정 |
3. 원자적 연산(Atomic Operation) 활용 | CPU에서 지원하는 원자적 연산을 사용하여 충돌 방지 |
✅ 1. 락(Lock) 사용 - Mutex로 임계 영역 보호
락(Lock)을 사용하면 한 번에 하나의 스레드만 임계 영역에 접근할 수 있도록 제한하여 경쟁 상태를 방지할 수 있습니다.
🟢 해결 코드: Mutex 적용
#include <stdio.h>
#include <pthread.h>
int balance = 1000; // 공유 자원 (임계 영역)
pthread_mutex_t lock; // 뮤텍스 선언
void* withdraw(void* arg) {
pthread_mutex_lock(&lock); // 🔒 락 획득 (임계 영역 보호)
int temp = balance;
temp -= 500;
balance = temp;
pthread_mutex_unlock(&lock); // 🔓 락 해제
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_mutex_init(&lock, NULL); // 뮤텍스 초기화
pthread_create(&t1, NULL, withdraw, NULL);
pthread_create(&t2, NULL, withdraw, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("최종 잔액: %d\n", balance); // 항상 500으로 출력됨
pthread_mutex_destroy(&lock); // 뮤텍스 제거
return 0;
}
✅ 실행 결과 (예측 가능)
최종 잔액: 500
🔹 락을 사용하면 한 번에 하나의 스레드만 balance를 수정할 수 있으므로 경쟁 상태가 발생하지 않음.
🔹 pthread_mutex_lock()을 사용하여 보호하고, 작업이 끝나면 반드시 pthread_mutex_unlock()으로 해제해야 함.
✅ 2. 스레드 간 순서 조정 - Condition Variable 사용
일부 경우에서는 Lock을 사용하지 않고 스레드 간 실행 순서를 조정하여 해결할 수도 있습니다.
🟢 해결 코드: Condition Variable 적용
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t lock;
pthread_cond_t cond;
int balance = 1000;
int done = 0;
void* withdraw(void* arg) {
pthread_mutex_lock(&lock);
while (!done) pthread_cond_wait(&cond, &lock); // 🔄 특정 조건이 충족될 때까지 대기
balance -= 500;
pthread_mutex_unlock(&lock);
return NULL;
}
void* notify(void* arg) {
pthread_mutex_lock(&lock);
done = 1;
pthread_cond_signal(&cond); // 🔔 대기 중인 스레드에게 신호 보내기
pthread_mutex_unlock(&lock);
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_mutex_init(&lock, NULL);
pthread_cond_init(&cond, NULL);
pthread_create(&t1, NULL, withdraw, NULL);
pthread_create(&t2, NULL, notify, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("최종 잔액: %d\n", balance);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
return 0;
}
✅ Condition Variable을 사용하면 특정 조건이 만족될 때만 스레드가 실행됨.
✅ 락 없이도 동기화가 가능하지만, 락과 함께 사용해야 안전함.
✅ 3. 원자적 연산(Atomic Operation) 활용
CPU가 제공하는 원자적 연산(Atomic Operation) 을 활용하면, 락을 사용하지 않고도 경쟁 상태를 방지할 수 있습니다.
C에서는 __sync_fetch_and_sub() 같은 GCC 내장 함수를 활용할 수 있습니다.
🟢 해결 코드: 원자적 연산 적용
#include <stdio.h>
#include <pthread.h>
int balance = 1000; // 공유 자원 (임계 영역)
void* withdraw(void* arg) {
__sync_fetch_and_sub(&balance, 500); // ✅ 원자적 연산 적용 (Atomic Operation)
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, withdraw, NULL);
pthread_create(&t2, NULL, withdraw, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("최종 잔액: %d\n", balance); // 항상 500이 유지됨
return 0;
}
✅ 원자적 연산은 CPU 명령어 단위에서 실행되므로 락 없이도 동기화 가능.
✅ 하지만 모든 연산이 원자적으로 처리되지는 않으며, 복잡한 연산에는 여전히 락이 필요.
✅ 정리
- 임계 영역(Critical Section) 은 여러 개의 스레드가 동시에 실행될 때 데이터 충돌이 발생할 가능성이 있는 코드 영역.
- 해결 방법:
- Lock 사용 (Mutex, RWLock)
- Condition Variable로 실행 순서 제어
- 원자적 연산(Atomic Operation) 활용
'정보기술 > 운영체제 (OS)' 카테고리의 다른 글
운영체제 락 개념과 동기화 기법 (2. 기본 락(Lock) / 2-2. 재진입 가능 락(Reentrant Lock, RLock)) (0) | 2025.02.26 |
---|---|
운영체제 락 개념과 동기화 기법 (2. 기본 락(Lock) / 2-1. 뮤텍스(Mutex)) (0) | 2025.02.26 |
운영체제 락 개념과 동기화 기법 (1. 운영체제에서 동기화 / 1-1. 동기화(Synchronization)란?) (0) | 2025.02.26 |
시스템 콜 (System Call) - 6. 시스템 콜의 보안과 성능 이슈 (0) | 2025.02.08 |
시스템 콜 (System Call) - 5. 시스템 콜을 직접 사용해 보기 (0) | 2025.02.08 |