파이썬 스레딩 Condition으로 재고 대기 알림 패턴 구현하기
⚙️ 실무에서 자주 쓰이는 동시성 패턴을 파이썬 코드로 쉽게 배우는 방법
프로그램을 만들다 보면 여러 작업이 동시에 일어나야 하는 경우가 많습니다.
특히 온라인 쇼핑몰이나 주문 시스템처럼 재고가 없을 때 대기하다가 입고가 되면 즉시 알려주는 기능은 실무에서 자주 요구됩니다.
이런 상황에서 파이썬의 threading.Condition 객체를 활용하면 대기와 알림의 흐름을 깔끔하게 제어할 수 있습니다.
이번 글에서는 재고 부족 시 대기 → 입고 시 알림이라는 전형적인 패턴을 중심으로, 어떻게 파이썬에서 이 과정을 구현할 수 있는지 살펴보겠습니다.
단순히 문법을 나열하는 대신, 실제 코드와 실행 흐름을 이해할 수 있도록 차근차근 설명드릴게요.
먼저 스레드(thread)의 기본 개념과 Condition이 제공하는 역할을 간단히 짚고, 이어서 코드 예제와 실행 결과를 통해 실무에 바로 적용할 수 있는 수준까지 정리할 예정입니다.
이 과정을 통해 동시성 프로그래밍이 어렵게만 느껴졌던 분들도 한결 쉽게 이해할 수 있을 거예요.
특히 백엔드 서버나 데이터 처리 파이프라인을 다루는 분들에게는 유용한 힌트가 될 것입니다.
📋 목차
🔗 스레드와 Condition의 기본 이해
파이썬에서 여러 작업을 동시에 처리하기 위해 가장 많이 활용되는 기능 중 하나가 바로 스레드(Thread)입니다.
스레드는 하나의 프로세스 안에서 여러 실행 흐름을 병렬로 수행할 수 있게 해주며, 네트워크 요청 처리나 데이터 처리, 사용자 이벤트 응답 같은 작업을 동시에 진행할 수 있도록 돕습니다.
하지만 여러 스레드가 동시에 같은 자원에 접근하려 하면 충돌이 발생할 수 있기 때문에, 이를 안전하게 제어할 수 있는 동기화 메커니즘이 필요합니다.
대표적인 동기화 도구로는 Lock, Semaphore, Condition이 있습니다.
이 중 Condition은 단순한 자원 잠금뿐 아니라, 특정 조건이 만족될 때까지 스레드를 대기(wait)시키고, 조건 충족 시 다른 스레드에서 알림(notify)을 보낼 수 있다는 특징이 있습니다.
즉, 단순히 자원을 차단하는 것에 그치지 않고, “조건이 충족될 때까지 기다린다”는 패턴을 구현할 수 있는 것이죠.
🧩 Condition이 필요한 이유
만약 단순히 Lock만 사용한다면, 한 스레드는 자원을 점유하고 다른 스레드는 그 자원이 풀리기를 기다려야만 합니다.
하지만 특정 상태(예: 재고가 0보다 커지는 상황)가 충족되기 전까지는 기다릴 필요가 없을 수도 있죠.
이럴 때 Condition을 사용하면 스레드가 조건을 만족할 때까지 효율적으로 대기하고, 다른 스레드에서 조건이 달성되었음을 알려주면 즉시 깨어나서 작업을 이어갈 수 있습니다.
💬 Condition은 단순한 락보다 한 단계 더 진보된 동기화 기법으로, “대기 → 알림” 패턴을 자연스럽게 구현할 수 있는 강력한 도구입니다.
🔑 핵심 포인트 정리
- ⚙️스레드는 동시에 여러 작업을 실행할 수 있게 한다
- 🔒Lock은 자원 접근을 통제하지만 조건 대기 기능은 부족하다
- 📢Condition은 특정 조건이 충족될 때까지 효율적으로 스레드를 대기시킬 수 있다
🛠️ 파이썬 Condition으로 재고 대기 구현
이번에는 실제 코드로 재고 대기 패턴을 구현해 보겠습니다.
시나리오는 단순합니다. 소비자 스레드는 재고가 없으면 기다려야 하고, 생산자 스레드가 재고를 추가하면 소비자가 깨어나서 상품을 가져가는 구조입니다.
이 과정을 자연스럽게 제어하기 위해 Condition 객체를 사용합니다.
import threading
import time
stock = 0
condition = threading.Condition()
def consumer():
global stock
with condition:
while stock == 0:
print("⏳ 재고가 없어 대기 중...")
condition.wait() # 재고가 생길 때까지 대기
stock -= 1
print("✅ 상품을 가져갔습니다. 남은 재고:", stock)
def producer():
global stock
time.sleep(2)
with condition:
stock += 1
print("📦 상품이 입고되었습니다. 현재 재고:", stock)
condition.notify() # 소비자에게 알림
threading.Thread(target=consumer).start()
threading.Thread(target=producer).start()
위 코드에서 소비자 스레드는 재고(stock)가 0일 경우 condition.wait()를 호출해 대기 상태로 들어갑니다.
프로듀서 스레드가 일정 시간이 지난 후 stock을 증가시키고 condition.notify()로 알림을 보내면, 소비자 스레드가 깨어나 작업을 이어갑니다.
📊 실행 결과 살펴보기
실행하면 먼저 소비자 스레드가 “⏳ 재고가 없어 대기 중…” 메시지를 출력하며 멈춰 있습니다.
잠시 후 생산자 스레드가 상품을 입고시키고, “📦 상품이 입고되었습니다” 메시지와 함께 소비자가 깨어나 “✅ 상품을 가져갔습니다”라는 메시지를 출력합니다.
이 과정을 통해 Condition이 정확히 “재고 대기 → 알림” 패턴을 수행하고 있음을 확인할 수 있습니다.
💎 핵심 포인트:
Condition을 활용하면 단순히 Lock으로 제어하는 것보다 훨씬 유연하게 “대기와 알림” 구조를 구현할 수 있습니다.
⚙️ 입고 알림과 소비자 스레드 동작 방식
Condition을 활용한 “재고 대기 → 알림” 패턴의 핵심은 wait()와 notify() 메서드의 상호작용입니다.
소비자 스레드는 조건을 만족하지 않으면 기다리고, 생산자 스레드는 조건을 만족시킨 뒤 소비자를 깨워주는 구조죠.
이때 중요한 것은 두 스레드 모두 condition 객체를 lock으로 소유한 상태에서만 이 메서드를 호출해야 한다는 점입니다.
🔔 notify와 notify_all의 차이
Condition에는 notify()와 notify_all() 두 가지 알림 방식이 있습니다.
notify는 대기 중인 스레드 중 하나만 깨우는 반면, notify_all은 모든 대기 스레드를 깨웁니다.
재고가 한 개 입고되었을 때 notify를 사용하면 한 명의 소비자만 깨어나 상품을 가져갈 수 있고, notify_all을 사용하면 여러 스레드가 동시에 깨어나 경합을 벌일 수 있습니다.
| 메서드 | 동작 방식 |
|---|---|
| notify() | 대기 중인 스레드 중 하나만 깨운다 |
| notify_all() | 대기 중인 모든 스레드를 깨운다 |
⚠️ 동작 시 주의할 점
⚠️ 주의: notify를 호출할 때 조건이 아직 충족되지 않았다면 소비자는 깨어나도 다시 기다리게 됩니다. 따라서 조건을 반드시 확인하는 while 루프와 함께 사용하는 것이 안전합니다.
즉, 소비자 스레드는 항상 “조건이 충족될 때까지 반복적으로 확인”해야 하며, 생산자는 조건을 만족시킨 시점에서 알림을 보내야 원하는 결과를 얻을 수 있습니다.
이런 방식이 없으면 의도치 않은 동작이나 무한 대기 상황이 발생할 수 있습니다.
🔌 생산자 소비자 패턴 확장하기
앞서 살펴본 예제는 생산자 한 명과 소비자 한 명이 존재하는 간단한 구조였습니다.
하지만 실제 서비스 환경에서는 여러 명의 소비자가 동시에 주문을 넣거나, 여러 생산자가 동시에 재고를 공급하는 경우가 많습니다.
이런 경우에도 Condition을 이용하면 효율적인 생산자-소비자 패턴을 구현할 수 있습니다.
👥 다중 소비자 처리
여러 소비자가 동시에 대기 중이라면 notify_all()을 사용하여 모든 소비자를 깨울 수 있습니다.
그 후 조건을 만족한 스레드만 작업을 이어가고, 나머지는 다시 대기 상태로 돌아갑니다.
이 방식은 경쟁 상태를 방지하면서도 자원을 효율적으로 분배하는 데 도움이 됩니다.
🏭 다중 생산자 처리
재고를 공급하는 쪽이 여러 스레드라면, 각각이 Condition을 통해 재고를 늘린 후 소비자에게 알림을 보낼 수 있습니다.
특히 동시에 재고가 늘어날 수 있기 때문에, 소비자는 반복문 안에서 현재 재고를 확인하며 안정적으로 처리해야 합니다.
def consumer(name):
global stock
with condition:
while stock == 0:
print(f"{name} ⏳ 대기 중...")
condition.wait()
stock -= 1
print(f"{name} ✅ 상품 구매 완료, 남은 재고: {stock}")
def producer(name):
global stock
with condition:
stock += 1
print(f"{name} 📦 상품 입고, 현재 재고: {stock}")
condition.notify_all()
이 코드를 실행하면 여러 소비자가 동시에 대기하다가, 생산자 스레드가 재고를 추가하면 한 명씩 차례로 상품을 가져가게 됩니다.
이 과정에서 notify_all을 사용했기 때문에 모든 소비자가 깨어나 경쟁하게 되지만, 조건 확인 로직을 통해 공정하게 자원이 분배됩니다.
💡 TIP: 다중 생산자-소비자 환경에서는 항상 조건문을 반복문과 함께 사용하여, 조건이 충족되지 않았을 때 재검증 과정을 거치는 습관을 들이는 것이 안전합니다.
💡 실무 적용 사례와 주의할 점
Condition을 활용한 “재고 대기 → 알림” 패턴은 이론적인 예제에 그치지 않고, 실제 서비스 환경에서도 널리 쓰입니다.
예를 들어 전자상거래 플랫폼의 주문 처리 시스템, 티켓 예매 서비스, 멀티스레드 데이터 처리 파이프라인 등에서 동일한 원리로 활용할 수 있습니다.
특히 동시 접속자가 많을 때 자원 관리가 중요한 상황에서는 Condition이 효율적인 도구가 됩니다.
📌 실무 적용 사례
- 🛒쇼핑몰 재고 관리 시스템: 재고가 없을 때 주문이 대기하고, 입고 시 소비자가 알림을 받아 처리
- 🎟️티켓 예매 시스템: 좌석이 빈 상태가 될 때까지 대기한 후, 빈 좌석이 발생하면 즉시 할당
- 📡데이터 처리 파이프라인: 데이터 버퍼가 비어있으면 소비자가 대기하고, 프로듀서가 데이터 입력 시 처리 시작
⚠️ 주의해야 할 점
⚠️ 주의: Condition을 사용할 때는 반드시 조건 확인을 while 루프 안에서 수행해야 합니다.
notify로 깨어난 스레드가 조건을 충족하지 못하면 다시 wait 상태로 돌아가야 하기 때문입니다.
또한 다중 스레드 환경에서는 경쟁 상태(race condition)와 데드락(deadlock) 위험이 존재합니다.
이를 방지하기 위해 Condition과 Lock을 적절히 조합하고, 공유 자원에 접근하는 모든 부분에서 일관된 제어를 유지해야 합니다.
조건문과 동기화 메커니즘을 잘못 사용하면 프로그램이 무한히 멈추거나, 의도치 않게 여러 소비자가 동시에 같은 자원을 가져가는 문제가 생길 수 있습니다.
💎 핵심 포인트:
Condition은 “효율적인 대기와 알림”을 보장하는 도구이지만, 올바른 사용법과 주의사항을 지켜야 안정적인 동작을 확보할 수 있습니다.
❓ 자주 묻는 질문 (FAQ)
Condition과 Lock은 어떤 차이가 있나요?
notify와 notify_all 중 어떤 것을 사용해야 하나요?
여러 스레드가 동시에 경쟁하도록 하려면 notify_all을 사용하면 됩니다.
while 루프 대신 if 조건문으로 wait을 사용할 수 있나요?
Condition 객체를 사용할 때 반드시 Lock이 필요한가요?
재고 관리 외에 Condition을 활용할 수 있는 사례가 있나요?
notify가 호출되었는데 소비자가 깨어나지 않는 경우가 있나요?
Condition을 asyncio 같은 비동기 프로그래밍에서도 쓸 수 있나요?
멀티프로세스 환경에서도 Condition을 사용할 수 있나요?
📌 파이썬 Condition으로 구현하는 대기와 알림 패턴 정리
파이썬에서 threading.Condition은 단순한 Lock보다 한 단계 발전된 동기화 기법으로, “조건 충족 시까지 대기 → 조건 만족 시 알림”이라는 구조를 깔끔하게 구현할 수 있게 해줍니다.
이번 글에서는 Condition의 기본 개념부터, 재고 대기 → 입고 알림 패턴의 코드 구현, notify와 notify_all의 차이, 다중 생산자-소비자 확장, 그리고 실무 적용 사례와 주의사항까지 전반적인 내용을 살펴봤습니다.
특히 재고 관리 시스템이나 티켓 예매, 데이터 처리 파이프라인처럼 자원 분배가 중요한 환경에서 Condition은 효율적인 대기와 알림을 통해 안정적인 동작을 보장할 수 있습니다.
다만 잘못 사용하면 데드락이나 경쟁 상태가 발생할 수 있으므로, 반드시 while 루프 안에서 조건을 확인하고 Lock과 함께 사용하는 습관이 필요합니다.
🏷️ 관련 태그 : 파이썬스레드, 파이썬동시성, Condition객체, 멀티스레드프로그래밍, 파이썬재고관리, notify와wait, 생산자소비자패턴, 파이썬예제, 백엔드시스템, 동기화