2025. 2. 26. 12:24ㆍ정보기술/운영체제 (OS)
📖 2-1. 뮤텍스(Mutex)
뮤텍스(Mutex, Mutual Exclusion)는 여러 개의 스레드가 동시에 공유 자원에 접근하는 것을 방지하는 동기화 기법입니다.
운영체제(OS) 내부에서 지원하는 기본적인 동기화 기법으로, 한 번에 하나의 스레드만 자원에 접근할 수 있도록 보장합니다.
✅ 뮤텍스란?
🔹 뮤텍스의 개념
- Mutex는 상호 배제(Mutual Exclusion) 를 의미하며, 한 번에 오직 하나의 스레드만 공유 자원(임계 영역)에 접근할 수 있도록 보장합니다.
- 스레드가 Mutex를 획득(lock)하면, 다른 스레드는 Mutex가 해제(unlock)될 때까지 대기 상태에 머뭅니다.
- pthread_mutex_t를 사용하여 구현되며, 운영체제가 직접 제공하는 동기화 도구 중 하나입니다.
🔹 뮤텍스의 특징
특징 | 설명 |
단일 소유권 | Lock을 획득한 스레드만 Unlock 가능 |
Blocking 방식 | 다른 스레드는 Mutex가 해제될 때까지 대기 |
경량 동기화 기법 | 커널 개입이 적어 성능 부담이 적음 |
데드락 위험 | 잘못된 사용 시 데드락 발생 가능 |
✅ 뮤텍스를 사용해야 하는 이유
멀티스레딩 환경에서 여러 개의 스레드가 동시에 공유 자원(Shared Resource) 에 접근하면 경쟁 상태(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 <-- 의도하지 않은 결과 발생 가능!
- 문제점: 두 개의 스레드가 balance -= 500 연산을 동시에 실행할 경우, 중간 연산 결과가 손실될 가능성이 높음.
- 해결 방법: 뮤텍스를 사용하여 한 번에 하나의 스레드만 자원을 수정하도록 제한해야 함.
✅ C 코드 예제 (Mutex 적용)
뮤텍스를 사용하여 임계 영역(Critical Section) 을 보호하는 코드입니다.
#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()으로 해제해야 함.
✅ 뮤텍스 사용의 장점과 단점
🟢 장점
✔ 경쟁 상태(Race Condition) 방지 → 공유 자원의 데이터 무결성을 보장
✔ 임계 영역 보호 → 하나의 스레드만 실행 가능
✔ 간단한 동기화 기법 → 구현이 비교적 쉬움
🔴 단점
❌ 데드락(Deadlock) 가능성 → Lock을 획득한 후 해제하지 않으면 프로그램이 멈춤
❌ 병목 현상(Bottleneck) → 락이 걸려있는 동안 다른 스레드는 대기해야 함
❌ 우선순위 역전(Priority Inversion) 문제 → 낮은 우선순위 스레드가 락을 오래 잡으면 높은 우선순위 스레드가 지연될 수 있음
✅ 데드락(Deadlock) 문제 해결 방법
🛑 데드락이 발생하는 코드
pthread_mutex_t lock1, lock2;
void* thread1(void* arg) {
pthread_mutex_lock(&lock1);
sleep(1); // 일부러 대기
pthread_mutex_lock(&lock2); // ⚠ 두 번째 락 대기 중 데드락 발생 가능
printf("Thread 1 실행 중...\n");
pthread_mutex_unlock(&lock2);
pthread_mutex_unlock(&lock1);
return NULL;
}
void* thread2(void* arg) {
pthread_mutex_lock(&lock2);
sleep(1);
pthread_mutex_lock(&lock1); // ⚠ Thread 1과 교착 상태 발생
printf("Thread 2 실행 중...\n");
pthread_mutex_unlock(&lock1);
pthread_mutex_unlock(&lock2);
return NULL;
}
✅ 해결 방법
- 락 획득 순서 정하기 → 항상 같은 순서로 Mutex를 획득해야 함.
- 타임아웃 적용 → pthread_mutex_timedlock()을 사용하여 일정 시간이 지나면 자동 해제.
- Try-Lock 사용 → pthread_mutex_trylock()을 사용하여 실패하면 다른 작업 수행.
✅ 뮤텍스 vs 세마포어 비교
기법 | 설명 | 차이점 |
뮤텍스 (Mutex) | 한 번에 하나의 스레드만 접근 가능 | Lock을 획득한 스레드만 해제 가능 |
세마포어 (Semaphore) | 여러 개의 스레드가 접근 가능 | count 값에 따라 동시 접근 허용 가능 |
✅ 정리
- 뮤텍스(Mutex) 는 상호 배제(Mutual Exclusion) 원칙을 적용하여 하나의 스레드만 임계 영역에 접근할 수 있도록 하는 락.
- 경쟁 상태(Race Condition) 문제를 방지하고, 데이터 무결성(Data Integrity)을 보장.
- 데드락(Deadlock) 방지를 위해 락 순서를 정하거나 타임아웃을 설정해야 함.
- 병렬 처리 성능이 중요한 경우 Semaphore 또는 Spinlock을 고려할 수 있음.