요약:

  • Python의 subprocess 모듈과 psutil 라이브러리는 지난 15년 동안 프로세스 종료 대기(wait()) 시 sleepwaitpid를 반복하는 비효율적인 'Busy-loop 폴링' 방식을 사용해왔음.
  • 이 방식은 불필요한 CPU Wake-up, 배터리 소모, 프로세스 종료 감지 지연(Latency) 문제를 야기하며, 다수의 프로세스를 모니터링할 때 확장성(Scalability)이 떨어짐.
  • 최근 업데이트를 통해 Linux에서는 pidfd_open()poll(), BSD/macOS에서는 kqueue()를 활용한 진정한 '이벤트 기반 대기(Event-driven waiting)'가 구현됨.
  • Windows는 이미 WaitForSingleObject를 사용하고 있어 변경 사항이 없으나, POSIX 시스템에서는 불필요한 컨텍스트 스위칭이 제거되고 CPU 사용량이 '0'에 수렴하게 됨.

상세요약:
1. 15년 동안 지속된 문제: Busy-loop 폴링
Python 3.3에서 subprocess.Popen.wait()timeout 파라미터가 추가된 이래로, Python 표준 라이브러리와 널리 쓰이는 psutil 라이브러리는 프로세스 종료를 기다리기 위해 비효율적인 방식을 사용해왔습니다.

기존 로직은 다음과 같이 단순하지만 비효율적이었습니다:

  1. waitpid(WNOHANG)으로 프로세스 상태 확인 (Non-blocking)
  2. 종료되지 않았으면 잠시 sleep() (Exponential backoff 적용)
  3. 1번으로 돌아가 반복
# 기존 방식 (개념 코드)  
import time, os  
  
def wait_busy(pid, timeout):  
    delay = 0.0001  
    while True:  
        # 프로세스 종료 여부 확인 (polling)  
        if os.waitpid(pid, os.WNOHANG) == (pid, status):  
            return status  
        time.sleep(delay)  
        delay = min(delay * 2, 0.040) # 최대 40ms까지 대기 시간 증가  
  

이 방식은 다음과 같은 3가지 치명적인 단점이 있습니다.

  • CPU Wake-ups: 아무리 대기 시간을 늘려도 시스템은 주기적으로 깨어나 상태를 확인해야 하므로 CPU 사이클을 낭비하고 전력을 소모합니다.
  • Latency (지연): 프로세스가 실제로 종료된 시점과 sleep에서 깨어나 이를 감지하는 시점 사이에 필연적인 시간 차이가 발생합니다.
  • Scalability (확장성): 수백, 수천 개의 프로세스를 동시에 모니터링해야 하는 서버 환경에서는 이러한 오버헤드가 급격히 증가합니다.

2. 해결책: POSIX 시스템을 위한 이벤트 기반 대기 (Event-driven Waiting)
모든 POSIX 시스템은 파일 디스크립터(File Descriptor)의 상태 변화를 감지하는 메커니즘(select, poll, epoll, kqueue)을 제공합니다. 최근 Python과 psutil은 이를 프로세스 PID 감지에 활용하는 방식으로 개선되었습니다.

  • Linux: 2019년 Linux 5.3 커널에 도입된 pidfd_open() 시스템 콜을 활용합니다. 이는 프로세스 PID를 가리키는 파일 디스크립터를 반환하며, 이를 poll()이나 epoll()에 등록하여 프로세스 종료 이벤트를 감시할 수 있습니다. (Python 3.9부터 os 모듈에 추가됨)
  • BSD / macOS: kqueue() 시스템 콜의 EVFILT_PROC 필터를 사용하여 프로세스 이벤트를 효율적으로 모니터링합니다.
  • Windows: 이미 WaitForSingleObject API를 통해 이벤트 기반 대기를 지원하고 있었으므로 변경 사항이 없습니다.

3. 성능 개선 및 결과
이 변화를 통해 wait() 호출 시 프로세스는 커널 입장에서 'Interruptible sleep' 상태가 됩니다. 즉, CPU를 전혀 소모하지 않고 커널 공간에서 조용히 대기하다가, 프로세스 종료 시그널이 발생하면 즉시 깨어납니다.

/usr/bin/time -v 등을 통해 벤치마킹한 결과, 기존 방식 대비 불필요한 컨텍스트 스위칭(Context Switching)이 획기적으로 감소했으며, 프로세스 종료 감지 속도 또한 즉각적으로 개선되었습니다. 이 업데이트는 psutil 라이브러리와 CPython 코어에 반영되어, 향후 Python 개발자들은 별도의 코드 수정 없이 성능 향상 혜택을 누릴 수 있게 되었습니다.