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)으로 수행한다:
- lock을 release
- 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()로 해결한다.
'개념 공부' 카테고리의 다른 글
| 운영체제 아주 쉬운 세가지 이야기 12주차(병행성 관련 버그) (0) | 2025.11.25 |
|---|---|
| 운영체제 아주 쉬운 세가지 이야기 11주차(세마 포어) (0) | 2025.11.18 |
| 운영체제 아주 쉬운 세가지 이야기 10주차(락) (1) | 2025.11.11 |
| 운영체제 아주 쉬운 세가지 이야기 9주차(쓰레드 API) (0) | 2025.11.03 |
| 운영체제 아주 쉬운 세가지 이야기 9주차(병행성: 개요) (0) | 2025.11.03 |