운영체제 락 개념과 동기화 기법 (2. 기본 락(Lock) / 2-3. 읽기-쓰기 락(Read-Write Lock, RWLock))

2025. 2. 26. 12:24정보기술/운영체제 (OS)

📖 2-3. 읽기-쓰기 락(Read-Write Lock, RWLock)

읽기-쓰기 락(Read-Write Lock, RWLock)은 읽기 작업은 여러 개의 스레드가 동시에 수행할 수 있지만, 쓰기 작업은 오직 하나의 스레드만 수행할 수 있도록 제한하는 동기화 기법입니다.
멀티스레딩 환경에서 읽기 작업이 많고, 쓰기 작업이 상대적으로 적은 경우 성능을 향상시키는 데 유용합니다.


✅ 읽기-쓰기 락이란?

🔹 기본 개념

  • 읽기(Read) 락: 여러 개의 스레드가 동시에 공유 자원을 읽을 수 있음.
  • 쓰기(Write) 락: 하나의 스레드만 공유 자원을 수정할 수 있으며, 쓰기 중에는 어떤 스레드도 접근할 수 없음.
  • pthread_rwlock_t 를 사용하여 구현.

🔹 읽기-쓰기 락의 필요성

  • 일반 Mutex를 사용하면 쓰기뿐만 아니라 읽기 작업도 단일 스레드만 수행해야 하므로 성능이 저하될 수 있음.
  • RWLock을 사용하면 여러 개의 스레드가 동시에 읽을 수 있도록 하여 동시성(Concurrency) 향상.

✅ 일반 Mutex와 RWLock 비교

동기화 방식 읽기(Read) 접근 쓰기(Write) 접근 사용 예시
Mutex (PTHREAD_MUTEX_NORMAL) ❌ 하나의 스레드만 접근 가능 ❌ 하나의 스레드만 접근 가능 데이터 변경이 자주 발생하는 경우
읽기-쓰기 락 (PTHREAD_RWLOCK) ✅ 여러 개의 스레드가 동시에 읽기 가능 ❌ 하나의 스레드만 접근 가능 읽기 작업이 많은 경우

✅ 동기화가 없는 경우 (문제 발생)

동기화 없이 여러 개의 스레드가 동시에 읽고 쓰기를 수행하면 데이터 충돌(Race Condition) 이 발생할 수 있습니다.

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int shared_data = 0; // 공유 자원

void* reader(void* arg) {
    printf("Reader %ld: 읽기 작업 수행, 값: %d\n", (long)arg, shared_data);
    return NULL;
}

void* writer(void* arg) {
    shared_data++;
    printf("Writer %ld: 쓰기 작업 수행, 새로운 값: %d\n", (long)arg, shared_data);
    return NULL;
}

int main() {
    pthread_t r1, r2, w1;

    pthread_create(&r1, NULL, reader, (void*)1);
    pthread_create(&w1, NULL, writer, (void*)1);
    pthread_create(&r2, NULL, reader, (void*)2);

    pthread_join(r1, NULL);
    pthread_join(w1, NULL);
    pthread_join(r2, NULL);

    return 0;
}

⚠ 실행 결과 (예측 불가한 결과 발생)

Reader 1: 읽기 작업 수행, 값: 0
Writer 1: 쓰기 작업 수행, 새로운 값: 1
Reader 2: 읽기 작업 수행, 값: 0  <-- ❌ 수정된 데이터가 반영되지 않음!
  • 문제점:
    • Reader 2가 Writer 1의 수정된 데이터를 반영하지 못함.
    • 여러 개의 스레드가 동시에 접근하면서 데이터 정합성(Data Consistency) 문제가 발생할 가능성이 높음.
    • 해결 방법: RWLock을 사용하여 동기화.

✅ C 코드 예제 (RWLock 적용)

읽기-쓰기 락을 적용하여 읽기 작업은 여러 개의 스레드가 동시에 수행할 수 있도록 하고, 쓰기 작업은 오직 하나의 스레드만 수행하도록 설정합니다.

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int shared_data = 0; // 공유 자원
pthread_rwlock_t rwlock; // 읽기-쓰기 락 선언

void* reader(void* arg) {
    pthread_rwlock_rdlock(&rwlock); // 🔒 읽기 락 획득
    printf("Reader %ld: 읽기 작업 수행, 값: %d\n", (long)arg, shared_data);
    pthread_rwlock_unlock(&rwlock); // 🔓 락 해제
    return NULL;
}

void* writer(void* arg) {
    pthread_rwlock_wrlock(&rwlock); // 🔒 쓰기 락 획득
    shared_data++;
    printf("Writer %ld: 쓰기 작업 수행, 새로운 값: %d\n", (long)arg, shared_data);
    pthread_rwlock_unlock(&rwlock); // 🔓 락 해제
    return NULL;
}

int main() {
    pthread_t r1, r2, w1;

    pthread_rwlock_init(&rwlock, NULL); // RWLock 초기화

    pthread_create(&r1, NULL, reader, (void*)1);
    pthread_create(&w1, NULL, writer, (void*)1);
    pthread_create(&r2, NULL, reader, (void*)2);

    pthread_join(r1, NULL);
    pthread_join(w1, NULL);
    pthread_join(r2, NULL);

    pthread_rwlock_destroy(&rwlock); // RWLock 제거
    return 0;
}

✅ 실행 결과 (올바른 동기화)

Reader 1: 읽기 작업 수행, 값: 0
Writer 1: 쓰기 작업 수행, 새로운 값: 1
Reader 2: 읽기 작업 수행, 값: 1  ✅ (수정된 값이 반영됨)
  • 읽기 작업은 여러 개의 스레드가 동시에 수행 가능.
  • 쓰기 작업은 한 번에 하나의 스레드만 수행 가능.
  • 쓰기 작업이 완료된 후, 모든 읽기 작업이 최신 데이터를 참조함.

✅ RWLock 사용 시 주의할 점

🔴 주의해야 할 문제

  1. 읽기 우선(RWLock이 너무 많은 읽기 작업을 허용하는 경우)
    • 읽기 스레드가 너무 많으면 쓰기 스레드가 기다려야 하는 문제 발생.
    • 해결책: 쓰기 우선 정책 적용 (pthread_rwlock_wrlock()을 먼저 수행).
  2. 우선순위 역전 (Priority Inversion)
    • 낮은 우선순위의 쓰기 스레드가 계속 대기하는 문제가 발생할 수 있음.
    • 해결책: 쓰기 스레드의 우선순위를 높이거나, Fair RWLock 구현.
  3. 데드락 (Deadlock)
    • 여러 개의 스레드가 동시에 읽기-쓰기 락을 서로 기다리는 경우 데드락 발생 가능.
    • 해결책: 락 획득 순서 조정.

✅ RWLock vs 일반 Mutex 비교

특징  일반 Mutex (PTHREAD_MUTEX_NORMAL) RWLock (PTHREAD_RWLOCK)
읽기 작업 동시 수행 가능 여부 ❌ 불가능 (하나의 스레드만 접근 가능) ✅ 가능 (여러 개의 스레드가 읽기 가능)
쓰기 작업 동시 수행 가능 여부 ❌ 불가능 ❌ 불가능 (하나의 스레드만 가능)
성능 쓰기와 읽기 모두 병목 발생 가능 읽기 작업이 많은 경우 성능 향상
사용 예시 읽기와 쓰기 작업이 비슷한 비율로 수행되는 경우 읽기 작업이 많은 경우 (예: 데이터베이스 캐시)

✅ 정리

  1. 읽기-쓰기 락(RWLock)은 읽기 작업은 여러 개의 스레드가 동시에 수행할 수 있도록 하고, 쓰기 작업은 오직 하나의 스레드만 수행하도록 제한하는 락.
  2. pthread_rwlock_t를 사용하여 구현하며, pthread_rwlock_rdlock()과 pthread_rwlock_wrlock()을 활용하여 동기화.
  3. 읽기 작업이 많은 경우 RWLock을 사용하면 성능을 향상시킬 수 있음.
  4. 하지만, 잘못된 사용 시 쓰기 작업이 계속 대기하는 문제(읽기 우선 현상)가 발생할 수 있음.
  5. 적절한 정책과 우선순위를 설정하여 RWLock을 효과적으로 활용해야 함.