운영체제 락 개념과 동기화 기법 (2. 기본 락(Lock) / 2-2. 재진입 가능 락(Reentrant Lock, RLock))
2025. 2. 26. 12:24ㆍ정보기술/운영체제 (OS)
📖 2-2. 재진입 가능 락(Reentrant Lock, RLock)
재진입 가능 락(Reentrant Lock, RLock) 은 같은 스레드가 여러 번 Lock을 획득할 수 있도록 허용하는 락입니다.
일반적인 Mutex는 한 번만 Lock을 획득할 수 있으며, 같은 스레드가 다시 Lock을 시도하면 데드락(Deadlock) 이 발생합니다.
하지만, RLock을 사용하면 동일한 스레드가 여러 번 Lock을 획득해도 데드락 없이 정상적으로 실행됩니다.
✅ 재진입 가능 락이란?
🔹 기본 개념
- 같은 스레드가 여러 번 Lock을 획득할 수 있도록 허용하는 락.
- 락을 획득한 횟수만큼 Unlock을 호출해야 해제됨.
- PTHREAD_MUTEX_RECURSIVE 속성을 설정하여 사용할 수 있음.
🔹 일반 Mutex와 RLock 비교
락 종류 | 같은 스레드가 여러 번 Lock 가능 여부 | 설명 |
일반 Mutex (PTHREAD_MUTEX_NORMAL) | ❌ 불가능 | 같은 스레드가 다시 Lock을 시도하면 데드락 발생 |
재진입 가능 락 (PTHREAD_MUTEX_RECURSIVE) | ✅ 가능 | 같은 스레드가 여러 번 Lock을 획득할 수 있으며, 해제도 동일한 횟수 필요 |
🔹 왜 RLock이 필요한가?
- 재귀 호출(Recursive Function) 에서 동일한 Lock을 여러 번 호출해야 할 경우 사용.
- 객체 지향 프로그래밍(OOP)에서 클래스 내부의 여러 메서드가 동일한 Lock을 사용해야 할 때 유용.
- 한 스레드가 동일한 락을 여러 번 획득해야 하는 복잡한 동기화 구조에서 필요.
✅ 일반 Mutex의 문제점 (재진입 불가능)
기본 Mutex를 사용하면 같은 스레드가 다시 Lock을 획득하려고 할 때 데드락이 발생합니다.
🛑 동기화 문제 발생 예제 (일반 Mutex 사용)
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t lock;
void recursive_function(int n) {
if (n <= 0) return;
pthread_mutex_lock(&lock); // 🔒 락 획득
printf("재귀 호출: %d\n", n);
recursive_function(n - 1); // ⚠ 같은 스레드에서 다시 Lock 시도 (데드락 발생)
pthread_mutex_unlock(&lock); // 🔓 락 해제
}
int main() {
pthread_mutex_init(&lock, NULL);
recursive_function(3); // 재귀 함수 호출
pthread_mutex_destroy(&lock);
return 0;
}
⚠ 실행 결과
재귀 호출: 3
(프로그램이 멈춤 - Deadlock 발생)
- 문제점:
- recursive_function() 내부에서 같은 Mutex를 여러 번 획득하려고 시도함.
- pthread_mutex_lock()이 중첩 호출되면서 자기 자신을 기다리는 상태(Deadlock) 가 발생하여 프로그램이 멈춤.
✅ 해결 방법: PTHREAD_MUTEX_RECURSIVE 사용 (RLock)
위의 문제를 해결하려면 재진입 가능 락(RLock) 을 사용해야 합니다.
이를 위해 PTHREAD_MUTEX_RECURSIVE 속성을 설정한 Mutex를 사용합니다.
🟢 해결 코드: RLock 적용
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t rlock; // 재진입 가능 락
void recursive_function(int n) {
if (n <= 0) return;
pthread_mutex_lock(&rlock); // 🔒 락 획득
printf("재귀 호출: %d\n", n);
recursive_function(n - 1); // ✅ 동일한 스레드에서 다시 Lock 가능
pthread_mutex_unlock(&rlock); // 🔓 락 해제
}
int main() {
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&rlock, &attr); // ✅ 재진입 가능 락 초기화
recursive_function(3); // 재귀 함수 호출
pthread_mutex_destroy(&rlock);
pthread_mutexattr_destroy(&attr);
return 0;
}
✅ 실행 결과
재귀 호출: 3
재귀 호출: 2
재귀 호출: 1
(정상 종료)
- 같은 스레드에서 여러 번 Lock을 호출해도 데드락 없이 정상 실행됨.
- Unlock도 획득한 횟수만큼 호출해야 락이 완전히 해제됨.
✅ RLock 사용 시 주의할 점
🔴 주의해야 할 문제
- Unlock 횟수 부족 시 Deadlock
- Lock을 여러 번 획득했으면 반드시 같은 횟수만큼 Unlock을 호출해야 함.
- 만약 Unlock을 덜 호출하면 락이 해제되지 않아서 다른 스레드가 무한 대기 상태에 빠짐.
- 성능 오버헤드
- 일반 Mutex보다 성능이 다소 느릴 수 있음.
- 불필요하게 재진입 가능한 락을 사용하면 오히려 성능 저하를 초래할 수 있음.
- 필요하지 않은 경우에는 PTHREAD_MUTEX_NORMAL을 사용하는 것이 좋음.
- 우선순위 역전(Priority Inversion)
- 낮은 우선순위 스레드가 락을 여러 번 획득한 상태에서 높은 우선순위 스레드가 대기하면 성능 저하가 발생할 수 있음.
✅ RLock vs 일반 Mutex 비교
특징 | 일반 Mutex (PTHREAD_MUTEX_NORMAL) | RLock (PTHREAD_MUTEX_RECURSIVE) |
재진입 가능 여부 | ❌ 불가능 (같은 스레드가 다시 Lock을 호출하면 Deadlock) | ✅ 가능 (같은 스레드가 여러 번 Lock 가능) |
사용 예시 | 일반적인 락이 필요한 경우 | 재귀 함수, 동일 스레드가 여러 번 락을 획득해야 하는 경우 |
Unlock 횟수 | 한 번만 호출해야 해제됨 | Lock을 획득한 횟수만큼 호출해야 해제됨 |
성능 | 더 빠름 | 약간의 오버헤드 존재 |
데드락 가능성 | 같은 스레드가 다시 Lock을 호출하면 데드락 발생 | 같은 스레드에서 여러 번 Lock 가능 |
✅ 정리
- 재진입 가능 락(RLock)은 같은 스레드가 여러 번 Lock을 획득할 수 있도록 허용하는 락.
- PTHREAD_MUTEX_RECURSIVE 속성을 사용하면 일반 Mutex와 달리 같은 스레드에서 여러 번 Lock 가능.
- 재귀 함수 및 동일 스레드가 여러 번 락을 사용해야 하는 상황에서 유용.
- 하지만, 필요하지 않은 경우에는 일반 Mutex를 사용하는 것이 더 성능에 유리.
- 사용 시 Unlock을 획득한 횟수만큼 호출해야 정상적으로 락이 해제됨.