본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] Subprocess 비동기 실행 및 결과 스트리밍 방법 3가지와 해결 전략

by Papa Martino V 2026. 2. 26.
728x90

Subprocess
Subprocess

서론: 왜 Subprocess의 비동기 스트리밍이 중요한가?

파이썬으로 시스템 자동화 도구나 백엔드 서버를 개발하다 보면 외부 쉘 명령어나 바이너리 파일을 실행해야 할 때가 많습니다. 하지만 기존의 subprocess.run()이나 check_output() 같은 동기 방식은 명령어가 완료될 때까지 전체 메인 루프를 차단(Blocking)해버리는 치명적인 단점이 있습니다. 특히 대용량 데이터를 처리하는 외부 프로세스나 실시간 로그를 확인해야 하는 작업의 경우, 프로세스가 끝날 때까지 기다렸다가 한꺼번에 결과를 받는 방식은 메모리 부족을 유발하거나 사용자 경험을 크게 저하시킵니다. 본 포스팅에서는 asyncio를 활용하여 외부 프로세스를 비동기적으로 실행하고, 출력을 실시간으로 가로채는 전문적인 방법과 성능 차이를 심층 분석하여 완벽한 해결책을 제시합니다.


1. 동기 vs 비동기 Subprocess 처리의 핵심 차이 분석

프로세스 관리의 효율성을 높이기 위해서는 기술적 아키텍처의 차이를 명확히 인지해야 합니다.

구분 항목 동기 방식 (Standard Subprocess) 비동기 방식 (Asyncio Subprocess) 주요 차이 및 장점
실행 제어 프로세스 종료까지 코드 실행 중단 이벤트 루프 기반 병렬 실행 멀티태스킹 성능 향상
결과 획득 종료 후 통째로 반환 (Batch) 라인 단위 실시간 획득 (Streaming) 실시간 로그 모니터링 가능
메모리 사용 전체 출력을 메모리에 적재 스트림 처리로 메모리 점유 최소화 대용량 출력 처리 시 안정성 확보
타임아웃 해결 외부 시그널 제어의 한계 비동기 취소(Cancellation) 용이 좀비 프로세스 생성 방지

2. 비동기 Subprocess 스트리밍 활용 방법 03가지

방법 01: create_subprocess_exec을 이용한 저수준 제어

가장 강력하고 유연한 방법입니다. 인자를 리스트 형태로 전달하여 쉘 인젝션 위험을 방지하며, stdoutasyncio.subprocess.PIPE로 설정하여 스트림을 개방합니다.

방법 02: StreamReader를 통한 한 줄씩 읽기 (Line-by-Line)

프로세스의 표준 출력 스트림에서 readline()을 호출하여 실시간으로 터미널 출력을 가로채는 전략입니다. 이는 딥러닝 학습 로그나 실시간 서버 상태 모니터링에 해결책이 됩니다.

방법 03: 가변적 버퍼링 제어와 stderr 병합

오류 메시지(stderr)와 일반 메시지(stdout)를 동시에 비동기로 처리하여, 프로세스의 상태를 보다 정밀하게 파악하는 고수준 구현 전략입니다.

3. Sample Example: 실시간 로그 스트리밍 구현

아래 코드는 긴 시간이 소요되는 외부 명령어를 비동기로 실행하고, 매 라인마다 결과를 출력하는 실제 구현 사례입니다.


import asyncio
import sys

async def run_command_streaming(cmd, *args):
    # 1. 비동기 서브프로세스 생성
    process = await asyncio.create_subprocess_exec(
        cmd, *args,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )

    print(f"[*] 프로세스 시작 (PID: {process.pid})")

    # 2. 표준 출력 실시간 스트리밍 처리
    async def stream_output(stream, prefix):
        while True:
            line = await stream.readline()
            if not line:
                break
            # 결과 실시간 출력 (버퍼링 없이 즉시 전송)
            print(f"[{prefix}] {line.decode().strip()}")

    # stdout과 stderr을 동시에 처리
    await asyncio.gather(
        stream_output(process.stdout, "STDOUT"),
        stream_output(process.stderr, "STDERR")
    )

    # 3. 종료 코드 확인
    return_code = await process.wait()
    print(f"[*] 프로세스 종료 (Code: {return_code})")

# 실행 예시 (Unix 환경의 'ping' 명령어 시뮬레이션)
if __name__ == "__main__":
    if sys.platform == "win32":
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    
    # 5번의 핑을 보내는 명령어 비동기 실행
    asyncio.run(run_command_streaming("ping", "-c", "5", "google.com"))

4. 실무 발생 문제 해결 및 주의사항

비동기 서브프로세스 운용 시 흔히 발생하는 문제 중 하나는 Deadlock(교착 상태)입니다. wait()를 먼저 호출하고 스트림을 나중에 읽으려 하면 파이프 버퍼가 가득 차서 프로세스가 멈춰버립니다. 따라서 반드시 스트림을 먼저 소모(Consume)하거나 gather를 통해 병렬로 처리하는 방법을 택해야 합니다. 또한, 윈도우 환경에서는 SelectorEventLoop 대신 ProactorEventLoop를 사용해야 서브프로세스 비동기 기능이 정상 작동하므로 환경별 차이에 따른 초기화 코드가 필수적입니다.

결론: 시스템 효율성을 결정짓는 비동기 스트리밍

비동기 Subprocess 스트리밍은 단순한 코딩 스킬을 넘어, 리소스 최적화와 사용자 인터페이스의 반응성을 결정짓는 핵심 아키텍처입니다. 본 가이드에서 소개한 3가지 방법해결 전략을 적용함으로써, 무거운 외부 작업 중에도 중단 없는(Non-blocking) 고성능 파이썬 애플리케이션을 구축해 보시기 바랍니다.


콘텐츠 출처 및 참조 기술서

  • Python Standard Library: asyncio.subprocess Documentation (v3.12/v3.13)
  • Advanced Python Systems Programming: Handling I/O Bound Tasks
  • StackOverflow Engineering: Avoiding Deadlocks in Subprocess Pipes
  • PEP 3156 – Asynchronous IO Support (Guido van Rossum)
728x90