읽기-쓰기 락 (Read-Write Lock)

핵심 인사이트 (3줄 요약)

  1. 본질: 읽기-쓰기 락 (Read-Write Lock, RW Lock)은 다수의 독자가 동시에 읽을 수 있으며 단 하나의 저자만 독점적으로 쓸 수 있는 동기화 원시 객체로, 뮤텍스보다 읽기 집중 환경에서 높은 동시성을 제공한다.
  2. 가치: 읽기 연산이 쓰기보다 훨씬 빈번한 공유 자료구조(설정 객체, 캐시, 라우팅 테이블 등)에서 뮤텍스 대비 처리량을 N배(동시 독자 수만큼) 향상시킬 수 있다.
  3. 융합: POSIX pthread_rwlock_t, Java ReentrantReadWriteLock, C++ std::shared_mutex, Linux 커널의 rwlock_trw_semaphore가 표준 구현이며, RCU (Read-Copy-Update)는 독자 측 락조차 제거한 진화 형태다.

Ⅰ. 개요 및 필요성

뮤텍스는 모든 접근을 직렬화하므로, 읽기 작업도 순차적으로 처리된다. 하지만 읽기는 데이터를 수정하지 않으므로 여러 스레드가 동시에 읽어도 일관성이 깨지지 않는다. 읽기-쓰기 락은 이 특성을 활용하여 독자들의 동시 읽기를 허용하고, 저자만 독점 접근을 요구한다.

접근 규칙:

  • 독자 N명 ↔ 독자 M명: 허용 (동시 읽기)
  • 독자 N명 ↔ 저자 1명: 상호 배제 (읽기-쓰기 분리)
  • 저자 1명 ↔ 저자 1명: 상호 배제 (쓰기 독점)

💡 비유: 도서관의 참고서 — 여러 학생이 동시에 볼 수 있지만, 선생님이 내용을 수정할 때는 모두 자리를 비워야 한다.

┌──────────────────────────────────────────────────────────┐
│       읽기-쓰기 락 접근 매트릭스                         │
├──────────────────────────────────────────────────────────┤
│                                                          │
│           │  Reader      │  Writer                       │
│  ─────────┼──────────────┼──────────                     │
│  Reader   │    ✅ OK     │   ❌ Block                    │
│  Writer   │   ❌ Block   │   ❌ Block                    │
│                                                          │
│  처리량 향상:                                            │
│  뮤텍스: 최대 1×              (항상 직렬화)              │
│  RWLock: 최대 독자수×         (동시 읽기)                │
└──────────────────────────────────────────────────────────┘

📢 섹션 요약 비유: 읽기-쓰기 락은 도서관 열람실 — 여러 학생이 동시에 책을 펼칠 수 있지만, 사서가 서가를 재배치할 때는 모두 나가야 합니다.


Ⅱ. 아키텍처 및 핵심 원리

POSIX 구현

#include <pthread.h>

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

// 독자 (Reader)
pthread_rwlock_rdlock(&rwlock);   // 읽기 락 획득 (다른 독자와 공유)
read_shared_data();
pthread_rwlock_unlock(&rwlock);

// 저자 (Writer)
pthread_rwlock_wrlock(&rwlock);   // 쓰기 락 획득 (독점)
update_shared_data();
pthread_rwlock_unlock(&rwlock);

내부 상태 기계

┌──────────────────────────────────────────────────────────────┐
│        읽기-쓰기 락 내부 상태 전이                           │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  상태: [Idle] ──rdlock──▶ [ReadLocked: count=N]              │
│          │                      │                            │
│          │ wrlock          모든 독자 unlock                  │
│          ▼                      ▼                            │
│       [WriteLocked]  ◀── [Idle]                              │
│          │                                                   │
│       writlock 해제                                          │
│          ▼                                                   │
│        [Idle]                                                │
│                                                              │
│  변수:                                                       │
│  read_count : 현재 읽고 있는 독자 수                         │
│  write_pending : 대기 중인 저자 수                           │
│  write_locked : 쓰기 락 획득 여부                            │
│                                                              │
│  독자 우선 정책: write_pending 무시하고 새 독자 진입 허용    │
│  저자 우선 정책: write_pending > 0이면 새 독자 차단          │
└──────────────────────────────────────────────────────────────┘

[다이어그램 해설] RW Lock의 내부 구현은 독자 카운터와 쓰기 잠금 플래그를 원자적으로 관리한다. 독자 우선(Reader-Preference) 구현에서는 독자가 있으면 새 독자가 즉시 진입하여 저자 기아(Writer Starvation)가 발생할 수 있다. 저자 우선(Writer-Preference)에서는 그 반대다. 공정(Fair) 구현은 대기 큐로 기아를 방지하지만 처리량이 감소한다.

Java ReentrantReadWriteLock

import java.util.concurrent.locks.*;

ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock  = rwLock.readLock();
Lock writeLock = rwLock.writeLock();

// 캐시 읽기 (여러 스레드 동시)
readLock.lock();
try {
    return cache.get(key);
} finally {
    readLock.unlock();
}

// 캐시 갱신 (단독)
writeLock.lock();
try {
    cache.put(key, value);
} finally {
    writeLock.unlock();
}

읽기→쓰기 락 업그레이드 문제

┌──────────────────────────────────────────────────────────┐
│    읽기→쓰기 업그레이드 교착 상태 시나리오               │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  T1: readLock() 획득                                     │
│  T2: readLock() 획득                                     │
│                                                          │
│  T1: writeLock() 시도 → T2의 readLock 해제 대기          │
│  T2: writeLock() 시도 → T1의 readLock 해제 대기          │
│                                                          │
│  → 교착 상태! 두 스레드가 서로를 기다림                  │
│                                                          │
│  해결: 읽기를 포기하고 쓰기 락을 처음부터 획득           │
│  또는 StampedLock (Java 8+)의 tryConvertToWriteLock()    │
└──────────────────────────────────────────────────────────┘

[다이어그램 해설] 락 업그레이드(읽기→쓰기 변환)는 대부분의 RW Lock 구현에서 지원하지 않거나 교착 상태를 유발한다. Java의 StampedLock은 낙관적 읽기(Optimistic Read)와 tryConvertToWriteLock()으로 이 문제를 보다 안전하게 처리한다.

📢 섹션 요약 비유: 읽기→쓰기 업그레이드 교착은 두 학생이 서로 "당신이 책을 놓으면 내가 수정하겠다"고 고집하다가 영원히 기다리는 상황입니다.


Ⅲ. 융합 비교 및 다각도 분석

RW Lock vs Mutex vs RCU 비교

┌──────────────────┬────────────┬────────────┬──────────────┐
│ 항목             │ Mutex      │ RW Lock    │ RCU          │
├──────────────────┼────────────┼────────────┼──────────────┤
│ 동시 읽기        │ 불가       │ 가능       │ 가능         │
│ 독자 오버헤드    │ 락비용     │ 카운터비용 │ 사실상 0     │
│ 저자 레이턴시    │ 즉시(다음) │ 즉시(다음) │ Grace Period │
│ 기아 방지        │ 정책 필요  │ 정책 필요  │ 없음         │
│ 구현 복잡도      │ 낮음       │ 중간       │ 높음         │
│ 적합 환경        │ 균등 R/W   │ 읽기 집중  │ SMP 읽기 집중│
└──────────────────┴────────────┴────────────┴──────────────┘

📢 섹션 요약 비유: Mutex는 단차선, RW Lock은 버스 전용 차선(읽기)/일반 차선(쓰기) 분리, RCU는 차선 없는 자율 주행 고속도로입니다.


Ⅳ. 실무 적용 및 기술사적 판단

실무 시나리오

  1. 설정 객체 동기화: 마이크로서비스에서 수백 개 스레드가 설정을 초당 수만 번 읽고, 관리자가 분당 1번 업데이트. RW Lock으로 읽기 처리량 최대화.
  2. 데이터베이스 인덱스: B-Tree 인덱스의 읽기 접근에 RW Lock을 적용하여 SELECT 동시성 최대화, INSERT/UPDATE/DELETE만 독점.

도입 체크리스트

  • 읽기:쓰기 비율 측정: 90:10 이상이면 RW Lock 효과적. 50:50이면 Mutex와 성능 차이 미미.
  • 기아 방지 정책: 저자 기아가 허용되지 않는다면 공정 모드 설정 필요.
  • 업그레이드 없음 원칙: 읽기 락 보유 중 쓰기 락 획득 시도 금지.

안티패턴

  • 쓰기 집중 환경: 쓰기가 잦으면 독자 우선 RW Lock이 계속 락-언락을 반복해 오히려 뮤텍스보다 느림.
  • 읽기 락 범위 과대: 락을 오래 보유하면 저자 기아 심화.

📢 섹션 요약 비유: 읽기-쓰기 락은 쓰기가 매우 드문 환경에서만 약이 되는 처방 — 쓰기가 잦은 환경에서는 오히려 독이 됩니다.


Ⅴ. 기대효과 및 결론

구분MutexRW Lock (공정)
독자 8개 동시 처리량최대 8×
저자 기아없음정책 필요
구현 복잡도낮음중간

읽기-쓰기 락은 읽기 집중 환경에서 동시성을 극적으로 향상시키는 강력한 도구다. 단, 쓰기 비율, 기아 허용 여부, 락 업그레이드 필요성을 사전에 분석해야 올바른 선택이 가능하다.


📌 관련 개념 맵

개념관계
독자-저자 문제RW Lock이 해결하는 이론적 원형
RCURW Lock의 진화형 (독자 측 락 제거)
Java StampedLock낙관적 읽기 + 업그레이드 지원 확장
저자 기아RW Lock 설계 시 반드시 고려해야 할 위험
pthread_rwlock_tPOSIX 표준 구현

👶 어린이를 위한 3줄 비유 설명

  1. 읽기-쓰기 락은 도서관 규칙 — 여러 친구가 동시에 같은 책을 읽어도 되지만, 내용을 고치려면 모두 나가야 해요.
  2. 읽기가 쓰기보다 훨씬 많을 때(예: 읽기 100번, 쓰기 1번) 뮤텍스보다 훨씬 빠른 처리가 가능해요.
  3. 주의: 읽고 있던 사람이 "고칠게요"로 바꾸려 하면 교착 상태가 생길 수 있어요 — 항상 다시 줄을 서야 해요!