운영체제 락 개념과 동기화 기법 (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 사용 시 주의할 점

🔴 주의해야 할 문제

  1. Unlock 횟수 부족 시 Deadlock
    • Lock을 여러 번 획득했으면 반드시 같은 횟수만큼 Unlock을 호출해야 함.
    • 만약 Unlock을 덜 호출하면 락이 해제되지 않아서 다른 스레드가 무한 대기 상태에 빠짐.
  2. 성능 오버헤드
    • 일반 Mutex보다 성능이 다소 느릴 수 있음.
    • 불필요하게 재진입 가능한 락을 사용하면 오히려 성능 저하를 초래할 수 있음.
    • 필요하지 않은 경우에는 PTHREAD_MUTEX_NORMAL을 사용하는 것이 좋음.
  3. 우선순위 역전(Priority Inversion)
    • 낮은 우선순위 스레드가 락을 여러 번 획득한 상태에서 높은 우선순위 스레드가 대기하면 성능 저하가 발생할 수 있음.

✅ RLock vs 일반 Mutex 비교

특징  일반 Mutex (PTHREAD_MUTEX_NORMAL)  RLock (PTHREAD_MUTEX_RECURSIVE)
재진입 가능 여부 ❌ 불가능 (같은 스레드가 다시 Lock을 호출하면 Deadlock) ✅ 가능 (같은 스레드가 여러 번 Lock 가능)
사용 예시 일반적인 락이 필요한 경우 재귀 함수, 동일 스레드가 여러 번 락을 획득해야 하는 경우
Unlock 횟수 한 번만 호출해야 해제됨 Lock을 획득한 횟수만큼 호출해야 해제됨
성능 더 빠름 약간의 오버헤드 존재
데드락 가능성 같은 스레드가 다시 Lock을 호출하면 데드락 발생 같은 스레드에서 여러 번 Lock 가능

✅ 정리

  1. 재진입 가능 락(RLock)은 같은 스레드가 여러 번 Lock을 획득할 수 있도록 허용하는 락.
  2. PTHREAD_MUTEX_RECURSIVE 속성을 사용하면 일반 Mutex와 달리 같은 스레드에서 여러 번 Lock 가능.
  3. 재귀 함수 및 동일 스레드가 여러 번 락을 사용해야 하는 상황에서 유용.
  4. 하지만, 필요하지 않은 경우에는 일반 Mutex를 사용하는 것이 더 성능에 유리.
  5. 사용 시 Unlock을 획득한 횟수만큼 호출해야 정상적으로 락이 해제됨.