AI가 만들어낸 코드를 한 줄 한 줄 곱씹어 보던 중에 특이한 패턴을 발견했다.
@classmethod
def stop_processor(cls):
"""백그라운드 처리 스레드 중지"""
cls._is_running = False
cls._processing_queue.put(None)
나라면 스레드를 종료하기 위해서 그냥 is_running 플래그만 False로 바꿔놨을 것 같은데, 왜 Queue에다가 None이라는 이상한 값을 집어 넣는걸까?
포이즌 필 패턴
이는 안전하게 스레드를 종료하기 위한 포이즌 필(Poison Pill) 패턴이라고 한다.

실생활에서 보이는 포이즌 필 패턴 예시가 재미있다.
식당에서 "CLOSED" 사인을 걸어두는 것을 예로들 수 있다. 영업종료 시간이 다가오면 식당주인은 문에 "CLOSED" 사인을 걸어둔다. 이는 더 이상 새로운 손님이 오는 것을 막지만, 지금 가게에 있는 손님들을 내쫓지는 않는다. 그들이 음식을 다 먹고, 계산하고 나갈 수 있도록 한다. 포이즌 필 메세지는 이렇듯 새로운 태스크가 처리되는 것을 막으면서도 현재 태스크를 마무리하고 안전하게 종료될 수 있도록 해준다.

내 코드에서는 Queue에 들어갈 수 있는 메세지 타입들을 따로 정의한 바가 없었기때문에, AI는 편한 방법으로 "None"을 넣는 것을 택했던 것이다. (이놈!!! 헷갈렸잖아~)
@classmethod
def stop_processor(cls):
"""백그라운드 처리 스레드 중지"""
cls._is_running = False
cls._processing_queue.put(None)
@classmethod
def _process_queue(cls):
"""
큐에서 하나씩 꺼내서 순차 처리
"""
while cls._is_running:
try:
# 리플레이 요청 확인 (1초마다)
cls.check_replay_requests()
# 큐에서 항목 가져오기 (1초 타임아웃)
try:
item = cls._processing_queue.get(timeout=1)
except:
# 큐가 비어있으면 queue.Empty 예외 발생
continue
if item is None:
break # 종료 신호
# 튜플이면 (type, entry_id) 형태
if isinstance(item, tuple):
item_type, entry_id = item
if item_type == RouletteStatus.REPLAY.value:
cls._replay_roulette(entry_id)
else:
# 일반 entry_id
cls._spin_roulette(item)
time.sleep(1)
except Exception as e:
logger.error(f"룰렛 처리 오류: {e}")
_process_queue는 별도 스레드에서 돌아가는 코드다. 주석에 보이는 것 처럼 item이 None(포이즌 필)인지 체크 후 종료 신호로 판단되면 즉시 루프를 종료한다. 이 코드에서는 큐에서 item을 가져오는 1초동안 스레드가 블로킹이 되는데, 포이즌 필 메세지가 없었다면 다음 사이클에나 종료되었을 것이다. 포이즌 필 메세지로 인해 대기중이던 스레드가 즉시 깨어나서 원하는 타이밍에 안전하게 종료된다.
포이즌 필 패턴 사용시 주의할 점
컨슈머가 여러개인 경우, 컨슈머의 수 만큼 포이즌 필을 넣어주어야한다.
'프로그래밍 > Design Pattern' 카테고리의 다른 글
| 메멘토 패턴 (Memento Pattern): Rust로 구현 (0) | 2023.07.30 |
|---|