운영체제 아주 쉬운 세가지 이야기 11주차(컨디션 변수)

2025. 11. 18. 16:46개념 공부

💡 Condition Variable 완벽 정리 (Threads & Concurrency 핵심

1️⃣ 왜 Condition Variable이 필요한가?

스레드는 종종 “어떤 조건이 만족될 때까지 기다렸다가” 일을 해야 한다.
대표 예시:

  • 부모 스레드는 자식 스레드가 끝날 때까지 기다려야 한다 (join())
  • Producer는 버퍼가 비어야 push할 수 있고, Consumer는 버퍼가 차 있어야 pop할 수 있다
  • 특정 리소스가 free 될 때까지 blocking 해야 한다 (메모리 할당, 커넥션 풀 등)

이것을 spin으로 해결하면 CPU를 계속 소모한다. 따라서 잠들었다가(signal로 깨우는) 방식이 필요해지고, 이것이 Condition Variable이다.


2️⃣ Condition Variable의 두 가지 연산

함수 설명
pthread_cond_wait(&cv, &lock) 💤 lock을 잠깐 반납하고, 해당 CV 큐에서 잠든다. 누군가 signal() 할 때 다시 lock을 가진 상태로 깨어난다.
pthread_cond_signal(&cv) ⚡ CV 큐에서 기다리는 스레드 1개를 깨운다.

wait()는 반드시 잠들기 직전에 lock을 쥐고 있어야 한다.


3️⃣ 왜 wait은 lock을 받아야 하는가? (wakeup-wait race 방지)

❌ 잘못된 경우


Thread A: wait() 하려고 함
Thread B: signal() 호출
Thread A: 이제 wait() 들어감 → 하지만 signal은 이미 지나갔다 → 영원히 sleep!

이게 “wakeup-wait race” — 신호 유실 문제다.

✔ CV의 atomic guarantee

pthread_cond_wait()는 다음 두 동작을 원자적(atomic)으로 수행한다:

  1. lock을 release
  2. sleep 큐에 자신을 넣고 sleep

즉, signal이 절대 유실되지 않는다.


4️⃣ 반드시 기억해야 할 규칙 3개

✔ ① wait 조건은 if가 아니라 while로 검사하라


// 잘못된 코드
if (count == 0)
    pthread_cond_wait(&cv, &lock);

// 올바른 코드
while (count == 0)
    pthread_cond_wait(&cv, &lock);

Mesa semantics 때문: 깨웠다고 해서 곧바로 실행되는 것이 아니고, 그 사이에 다른 스레드가 먼저 state를 바꿔버릴 수 있다.

✔ ② wait() 호출 전 lock MUST be held

잠드는 과정에서 lock 반납 + sleep이 atomic하게 일어나기 때문에 반드시 lock을 쥐고 있어야 한다.

✔ ③ signal() 호출 시에도 lock을 쥐고 있는 것이 안전

signal을 보낸 시점과 state 변화가 정확히 연동된다.


5️⃣ Producer–Consumer 문제 (Bounded Buffer)

💥 실패한 코드 (if + 단일 CV)

문제점 2개:

  • if 조건 사용 → state가 변하면 이미 깨어난 스레드가 다시 중간 상태를 보지 못하고 잘못 소비함
  • 한 개의 CV → consumer가 consumer를 깨움 (잘못된 wakeup)

🎯 정답: while + 2개의 CV(empty / fill)


pthread_cond_t empty, fill;

producer:
    lock()
    while (count == MAX)
        wait(empty)
    put()
    signal(fill)
    unlock()

consumer:
    lock()
    while (count == 0)
        wait(fill)
    get()
    signal(empty)
    unlock()

✔ producer는 consumer만 깨움 ✔ consumer는 producer만 깨움 → deadlock 없이, 정확한 동기화!


6️⃣ Covering Condition 문제

메모리 할당 예시:

  • Thread A: allocate(100)
  • Thread B: allocate(10)
  • 둘 다 wait() 중
  • Thread C: free(50)

문제: signal()은 단 하나의 스레드만 깨우기 때문에,
A(100 기다리는 스레드)를 깨울 수도 있음 → A는 못 깨어나야 하는데!

🎯 해결: pthread_cond_broadcast()

모든 스레드를 깨움 → 각자 while 조건을 다시 검사 → 자신이 가능한지만 체크함


7️⃣ 실무에서 Condition Variable이 중요한 이유

🔹 (1) Connection Pool

DB 커넥션이 모두 점유되면 스레드를 sleep 시키고, 커넥션이 반환되면 signal()로 깨운다.

🔹 (2) Thread-safe Queue / Job Queue

빈 큐에서 pop 시 sleep → push하면 signal

🔹 (3) Web Server / Message Broker 내부 구조

worker thread는 메시지가 없을 때 sleep, push되면 signal로 깨움 (nginx, redis 내부도 이런 구조)

🔹 (4) OS 커널 wait queue

디스크 I/O 완료 대기(wait queue), 메모리 free 대기 등 구현에 사용됨


8️⃣ 요약 (면접용 핵심 bullet)

  • CV는 "조건이 만족될 때까지 잠드는" 메커니즘이다.
  • wait()는 lock을 release + sleep을 원자적으로 수행한다.
  • wakeup-wait race를 방지한다.
  • 조건은 항상 while로 검사한다 (Mesa semantics).
  • Producer–Consumer 문제는 2개의 CV(empty/fill)을 사용해 해결한다.
  • Covering condition 문제는 broadcast()로 해결한다.