본문 바로가기

Python subprocess 멈출 때, 이럴 땐 어떻게 해야 할까?

@somewhere.2025. 11. 20. 17:30

Python에서 subprocess 모듈을 사용하여 외부 프로세스를 실행할 때, 예상치 못하게 프로그램이 멈추거나 응답이 없는 경우가 발생하곤 합니다. 이러한 문제는 코드의 흐름을 방해하고 디버깅을 어렵게 만들 수 있습니다. subprocess가 멈추는 원인은 다양하며, 그 해결 방법 또한 원인에 따라 달라집니다. 오늘은 subprocess가 멈추는 주요 원인과 각 상황에 맞는 해결책을 알아보겠습니다. 프로세스 관리는 시스템 안정성에 매우 중요하므로, 이 문제에 대한 이해는 필수적입니다.




이유 1 입출력 버퍼링 문제

subprocess에서 프로그램이 멈추는 가장 흔한 원인 중 하나는 입출력 버퍼링 문제입니다. 외부 프로세스가 표준 출력(stdout)이나 표준 에러(stderr)로 데이터를 계속해서 출력하지만, Python 스크립트에서 이를 즉시 읽어가지 않으면 해당 버퍼가 가득 차서 외부 프로세스가 더 이상 데이터를 쓰지 못하게 됩니다. 결국, 외부 프로세스는 출력을 기다리다가 멈추게 됩니다. 특히, 실행하는 외부 프로그램이 많은 양의 로그를 생성하거나, 실시간으로 상태를 보고하는 경우 이러한 문제가 자주 발생합니다.

 

문제 유형 증상 원인
버퍼링 프로세스가 멈추고 응답이 없음 Python이 외부 프로세스의 stdout/stderr 버퍼를 제때 비우지 못함
데드락 프로세스가 영원히 대기 상태 두 프로세스(Python, 외부 프로세스)가 서로의 출력을 기다리며 교착 상태 발생

이 문제를 해결하기 위해서는 subprocess.PopenstdoutstderrPIPE로 설정하고, communicate() 메서드를 사용하거나, 별도의 스레드를 사용하여 출력을 지속적으로 읽어주는 방법을 사용할 수 있습니다. communicate()는 모든 출력을 버퍼링한 후 반환하므로, 메모리 문제가 발생할 수 있습니다. 따라서 대량의 출력이 예상되는 경우, 스레드를 활용하여 실시간으로 읽어들이는 것이 더 효율적일 수 있습니다.

 

핵심 포인트: subprocess.Popen에서 stdoutstderrPIPE로 설정하고, communicate() 메서드나 별도의 스레드를 통해 출력을 관리하는 것이 중요합니다.




이유 2 외부 프로세스의 무한 루프 또는 시간 초과

subprocess로 실행된 외부 프로그램 자체에 문제가 있을 수도 있습니다. 예를 들어, 외부 프로그램이 어떤 조건에서든 종료되지 않는 무한 루프에 빠지거나, 특정 작업 완료에 예상보다 훨씬 오랜 시간이 걸려 타임아웃이 발생할 수 있습니다. 이 경우 Python 스크립트는 외부 프로세스가 종료되기를 무한정 기다리게 되고, 결국 멈춘 것처럼 보이게 됩니다. 외부 프로세스의 정상적인 동작 여부를 확인하는 것이 우선입니다.

 

▶ 1단계: 실행할 외부 프로그램이 독립적으로 정상 작동하는지 확인합니다. (Python 스크립트 외부에서 직접 실행하여 테스트)

▶ 2단계: 프로그램 실행 시 타임아웃 설정을 고려합니다. subprocess.Popentimeout 인자를 사용하거나, communicate(timeout=...)을 활용하여 일정 시간 내에 완료되지 않으면 예외를 발생시킵니다.

▶ 3단계: 프로그램이 특정 입력이나 상태를 기다리는지 확인하고, 필요한 입력이 제대로 제공되고 있는지 검토합니다.

타임아웃 설정은 무한 대기를 방지하는 좋은 방법입니다. 예를 들어, subprocess.run 함수를 사용할 때 timeout 매개변수를 설정하면 지정된 시간 내에 프로세스가 종료되지 않으면 `TimeoutExpired` 예외가 발생합니다. 이 예외를 적절히 처리하여 프로그램이 멈추는 것을 방지할 수 있습니다.




이유 3 데드락 또는 교착 상태

앞서 언급한 버퍼링 문제와 유사하지만, 데드락(Deadlock) 또는 교착 상태는 좀 더 복잡한 상황을 의미합니다. 이는 두 개 이상의 프로세스가 서로 상대방의 자원을 기다리면서 무한히 대기하는 상태를 말합니다. 예를 들어, Python 스크립트가 외부 프로세스의 표준 입력을 기다리고, 동시에 외부 프로세스가 Python 스크립트의 표준 출력을 기다리는 경우 발생할 수 있습니다. 특히, Popen으로 두 개 이상의 프로세스를 생성하고 서로 통신하게 할 때 주의해야 합니다.

 

상황 문제 해결 방안
Python이 외부 프로세스의 입력 기다림 외부 프로세스가 더 이상 진행되지 않음 `stdin.write()` 후 `stdout` 또는 `stderr`을 즉시 읽거나, 스레드 사용
외부 프로세스가 Python의 출력을 기다림 Python 프로세스가 멈춘 것처럼 보임 Python이 stdout 또는 stderr으로 데이터를 계속 보내야 함
다중 프로세스 간 통신 순환 종속성으로 인한 데드락 communicate() 사용 시 주의, 스레드를 활용하여 각 통신 채널 관리

데드락을 방지하기 위한 가장 좋은 방법 중 하나는 각 프로세스가 필요로 하는 입력을 적시에 제공하고, 불필요하게 출력을 기다리지 않도록 설계하는 것입니다. 또한, select 모듈이나 threading 모듈을 사용하여 여러 입출력 스트림을 비동기적으로 처리하는 것이 효과적입니다. Popen 객체의 poll() 메서드를 주기적으로 호출하여 프로세스의 종료 여부를 확인하고, wait() 메서드만 의존하는 것을 피하는 것이 좋습니다. 비동기 처리는 이러한 문제를 해결하는 데 큰 도움이 됩니다.

 

핵심 포인트: 데드락은 여러 프로세스가 서로를 기다리면서 발생하므로, 통신 흐름을 명확히 하고 비동기적인 입출력 처리를 통해 이를 방지해야 합니다.




타임아웃 설정과 무한 대기 방지

Python의 `subprocess` 모듈을 사용하여 외부 프로세스를 실행할 때, 해당 프로세스가 예상치 못하게 무한정 실행되거나 응답이 없을 경우, 프로그램 전체가 멈춰버리는 상황을 겪을 수 있습니다. 이러한 문제를 방지하기 위한 가장 효과적인 방법 중 하나는 타임아웃(timeout) 설정을 활용하는 것입니다. 타임아웃을 설정하면 지정된 시간 내에 프로세스가 완료되지 않았을 경우, Python은 자동으로 해당 프로세스를 종료하고 예외를 발생시켜 프로그램의 정상적인 흐름을 유지할 수 있도록 합니다. 이는 자원 누수를 막고, 시스템 안정성을 확보하는 데 매우 중요합니다. 타임아웃 값은 프로세스의 예상 실행 시간을 고려하여 신중하게 설정해야 하며, 너무 짧게 설정하면 정상적인 프로세스도 강제 종료될 수 있으므로 주의가 필요합니다.

 

설정 요소 설명
`timeout` 인자 `subprocess.run()` 함수 등에서 지정할 수 있으며, 초 단위로 설정합니다.
`TimeoutExpired` 예외 타임아웃이 발생하면 이 예외가 발생하므로, `try-except` 블록으로 처리해야 합니다.
적절한 값 설정 프로세스의 정상적인 완료 시간을 고려하여 여유롭게 설정하는 것이 좋습니다.




출력 버퍼링과 교착 상태(Deadlock)

`subprocess` 모듈은 외부 프로세스와 통신할 때 표준 출력(stdout)과 표준 에러(stderr)를 캡처할 수 있습니다. 하지만 외부 프로세스가 매우 많은 양의 출력을 생성하거나, stdout과 stderr 버퍼가 꽉 찼음에도 불구하고 프로세스가 계속해서 출력하려고 할 때, 외부 프로세스는 더 이상 진행할 수 없어 멈추는 교착 상태에 빠질 수 있습니다. 이러한 교착 상태는 Python 스크립트가 해당 프로세스의 완료를 기다리며 멈추게 되는 원인이 됩니다. 이를 해결하기 위해서는 `subprocess.run` 또는 `subprocess.Popen` 사용 시 `stdout=subprocess.PIPE` 와 `stderr=subprocess.PIPE` 를 함께 사용하면서, 외부 프로세스의 출력을 주기적으로 읽어 버퍼를 비워주는 것이 중요합니다. 또한, `text=True` 옵션을 사용하여 문자열로 디코딩하거나, `encoding` 인자를 명시하는 것도 도움이 됩니다.

 

주의사항: stdout과 stderr를 PIPE로 캡처할 경우, 버퍼링 문제를 염두에 두고 출력을 적절히 처리하지 않으면 교착 상태가 발생할 수 있습니다.

`subprocess.Popen` 객체를 사용할 때, `communicate()` 메서드를 사용하면 stdout과 stderr의 모든 데이터를 읽고 프로세스가 종료될 때까지 기다려 줍니다. 하지만 `communicate()`를 호출하기 전에 `stdout`이나 `stderr`가 꽉 차버리면 문제가 발생할 수 있으므로, 비동기적으로 출력을 처리하거나, `wait()`와 `poll()` 메서드를 조합하여 상태를 확인하며 출력을 읽는 방식을 고려해야 합니다.

 

Python subprocess 멈출 때, 이럴 땐 어떻게 해야 할까?




입력(stdin) 대기 및 비정상 종료 처리

외부 프로세스가 실행 중에 사용자 입력(stdin)을 기다리거나, 특정 조건에서 입력을 받아야 하는데 Python 스크립트에서 해당 입력을 제공하지 않는 경우, 프로세스는 영원히 입력을 기다리며 멈춰 있을 수 있습니다. 이러한 상황 역시 프로그램 전체가 멈추는 주요 원인 중 하나입니다. 이를 방지하기 위해서는 외부 프로세스가 어떤 종류의 입력을 요구하는지 정확히 파악하고, 필요한 경우 `stdin`으로 데이터를 적절히 제공해야 합니다. `subprocess.run` 함수를 사용할 때 `input` 인자를 통해 초기 입력을 전달하거나, `subprocess.Popen`을 사용하여 `stdin.write()` 메서드로 동적으로 입력을 보낼 수 있습니다.

 

▶ 1단계: 외부 프로세스가 입력(stdin)을 요구하는지 확인합니다. 이는 프로세스의 동작 방식이나 관련 문서를 통해 알 수 있습니다.

▶ 2단계: 만약 입력이 필요하다면, `subprocess.run`의 `input` 인자를 사용하거나 `subprocess.Popen`에서 `stdin.write()`를 통해 데이터를 전달합니다.

▶ 3단계: 입력 후에는 `stdin.close()`를 호출하거나, `communicate()` 메서드를 사용하여 프로세스가 완료되기를 기다립니다.

더불어, 외부 프로세스가 예상치 못한 이유로 비정상적으로 종료(exit)될 때, Python 스크립트가 이를 제대로 감지하고 처리하지 못하면 오류가 발생하거나 무한 대기에 빠질 수 있습니다. `subprocess.run`의 `check=True` 옵션을 사용하면, 프로세스가 0이 아닌 종료 코드로 종료될 경우 `CalledProcessError` 예외를 발생시켜 문제를 즉시 파악할 수 있습니다. `subprocess.Popen`에서는 `poll()` 메서드를 사용하여 프로세스가 종료되었는지 주기적으로 확인하고, `wait()` 메서드를 사용하여 프로세스 종료를 기다리며 종료 코드를 얻을 수 있습니다. 이러한 오류 처리 메커니즘을 잘 구축하는 것이 중요합니다.




자식 프로세스 비정상 종료 및 무한 대기 상황

Python에서 `subprocess` 모듈을 사용하여 외부 명령어를 실행하다 보면, 예상치 못한 상황에 마주칠 때가 있습니다. 특히 자식 프로세스가 갑자기 종료되거나, 아무런 응답 없이 계속 실행만 되면서 멈춰버리는 경우입니다. 이러한 상황은 코드의 흐름을 완전히 막아버리기 때문에 신속하게 원인을 파악하고 해결해야 합니다. 가장 흔한 원인 중 하나는 자식 프로세스 자체의 오류입니다. 실행하려는 프로그램에 버그가 있거나, 필수 파일이 없거나, 권한 문제가 발생했을 때 비정상적으로 종료될 수 있습니다. 또한, 자식 프로세스가 많은 양의 데이터를 출력하거나, 입력이 필요한데 주 프로세스에서 이를 제대로 처리해주지 못할 때도 문제가 발생할 수 있습니다. 예를 들어, `subprocess.run()` 함수에서 `capture_output=True` 옵션을 사용했지만, 자식 프로세스가 생성하는 표준 출력이나 표준 오류가 버퍼를 초과할 경우, 프로세스가 더 이상 진행되지 못하고 멈추는 현상이 나타날 수 있습니다. 이처럼 자식 프로세스의 상태를 주의 깊게 관찰하고, 발생하는 로그나 오류 메시지를 꼼꼼히 확인하는 것이 중요합니다.

 

상황 원인 해결 방안
비정상 종료 프로그램 자체 오류, 파일 누락, 권한 문제, 환경 변수 설정 오류 프로그램 디버깅, 파일 및 권한 확인, 환경 변수 점검
무한 대기 (I/O 문제) 과도한 출력 버퍼링, 입력 대기 불일치, 데드락 발생 `PIPE` 사용 시 버퍼 관리, `communicate` 또는 `read` 활용, 타임아웃 설정
리소스 부족 메모리 부족, CPU 과부하 시스템 리소스 모니터링, 프로세스 최적화




타임아웃 설정 및 프로세스 강제 종료

`subprocess` 모듈은 외부 프로세스를 실행할 때 무한정 기다리지 않도록 돕는 강력한 도구들을 제공합니다. 가장 유용한 기능 중 하나는 타임아웃(timeout)을 설정하는 것입니다. `subprocess.run()` 함수에는 `timeout` 매개변수가 있어, 지정된 시간 내에 프로세스가 완료되지 않으면 자동으로 `subprocess.TimeoutExpired` 예외를 발생시킵니다. 이를 통해 무한 루프에 빠지거나 응답 없는 프로세스로 인해 프로그램 전체가 멈추는 것을 방지할 수 있습니다. 예를 들어, `subprocess.run(['long_running_command'], timeout=10)` 와 같이 사용하면, 10초 안에 명령이 끝나지 않으면 예외가 발생하게 됩니다.

 

▶ 타임아웃 설정 예시:

import subprocess

try:
result = subprocess.run(['ping', '-c', '5', 'google.com'], timeout=5, capture_output=True, text=True)
print("Command executed successfully:")
print(result.stdout)
except subprocess.TimeoutExpired:
print("Command timed out after 5 seconds.")
except Exception as e:
print(f"An error occurred: {e}")

만약 타임아웃 예외가 발생했거나, 특정 상황에서 프로세스를 강제로 종료해야 할 필요가 있다면 `Popen` 객체를 활용하는 것이 좋습니다. `subprocess.Popen`으로 프로세스를 시작한 후, `proc.poll()`을 주기적으로 호출하여 프로세스의 종료 여부를 확인할 수 있습니다. 프로세스가 여전히 실행 중이라면 `proc.kill()` 메서드를 호출하여 SIGKILL 신호를 보내 강제로 종료시킬 수 있습니다. `proc.terminate()`는 SIGTERM 신호를 보내 좀 더 graceful하게 종료를 시도하는 반면, `proc.kill()`은 즉시 종료를 강제합니다. 어떤 방법을 사용하든, 자식 프로세스가 예상대로 작동하지 않을 때를 대비하여 이러한 메커니즘을 마련해두는 것이 파이썬 스크립트의 안정성을 높이는 데 크게 기여합니다.

 

핵심 포인트: `subprocess.run()`의 `timeout` 매개변수를 사용하여 외부 프로세스에 실행 시간 제한을 두는 것이 가장 간단하고 효과적인 방법입니다. 복잡한 제어가 필요할 때는 `subprocess.Popen`과 `kill()` 또는 `terminate()` 메서드를 활용하여 프로세스를 강제 종료할 수 있습니다.

핵심 요약

• `subprocess`에서 프로세스가 멈추는 주된 이유는 프로그램 오류, I/O 문제, 리소스 부족 등입니다.
• `timeout` 매개변수를 사용하여 외부 프로세스의 최대 실행 시간을 제한하고 `subprocess.TimeoutExpired` 예외를 처리하세요.
• `subprocess.Popen` 객체를 활용하면 `kill()` 또는 `terminate()` 메서드로 프로세스를 강제 종료할 수 있습니다.
• 이러한 설정을 통해 예기치 않은 프로세스 멈춤 현상으로부터 프로그램을 보호하고 안정성을 높일 수 있습니다.




주요 질문 FAQ




Q. subprocess.run() 함수를 호출했는데 프로그램이 응답 없이 멈춰요. 어떤 원인일 수 있나요?

subprocess.run() 함수에서 프로그램이 멈추는 가장 흔한 원인 중 하나는 자식 프로세스가 표준 출력(stdout)이나 표준 오류(stderr)를 버퍼링하고 있는데, 파이썬 프로세스가 해당 출력을 읽어가지 못할 때입니다. 이로 인해 자식 프로세스가 더 이상 출력을 생성하지 못하고 대기 상태에 빠지면서 전체 프로그램이 멈추게 됩니다. 특히 파이썬 3.5 이전 버전의 subprocess.call 이나 Popen과 같이 명시적으로 stdout/stderr을 캡처하도록 설정하지 않은 경우 발생하기 쉽습니다.




Q. 자식 프로세스가 무한 루프에 빠지거나 특정 조건에서 계속 대기하는 경우, 파이썬 스크립트가 멈추는 것을 어떻게 방지할 수 있나요?

자식 프로세스가 무한 루프에 빠지거나 특정 조건에서 계속 대기하는 상황을 방지하기 위해 `subprocess.run()` 함수의 `timeout` 매개변수를 활용할 수 있습니다. `timeout`에 초 단위의 시간을 지정하면, 해당 시간 안에 자식 프로세스가 종료되지 않을 경우 `subprocess.TimeoutExpired` 예외가 발생하며 파이썬 스크립트는 이 예외를 처리하여 멈춤 현상을 막을 수 있습니다. 이렇게 되면 의도치 않게 오래 실행되는 프로세스를 강제로 종료시킬 수 있습니다.




Q. shell=True 옵션을 사용했을 때 subprocess가 멈추는 경우가 있나요? 어떤 점을 주의해야 할까요?

네, `shell=True` 옵션을 사용할 때도 멈춤 현상이 발생할 수 있습니다. 이 옵션은 쉘을 통해 명령어를 실행하기 때문에, 쉘의 특성이나 외부 명령어가 예상치 못한 방식으로 동작할 때 문제가 발생할 수 있습니다. 예를 들어, 쉘 스크립트 내부에 무한 루프가 있거나, 사용자 입력을 기다리는데 입력을 제공하지 못하는 경우, 또는 쉘 자체의 오류로 인해 프로세스가 정상적으로 종료되지 못할 수 있습니다. `shell=True`는 보안상 위험할 수도 있으므로, 꼭 필요한 경우가 아니라면 `shell=False` (기본값)로 두고 명령어를 리스트 형태로 전달하는 것이 더 안전하고 예측 가능한 결과를 가져옵니다.




Q. 외부 명령어를 실행했는데, 실행 파일 경로가 잘못되었거나 권한 문제가 있어서 멈추는 경우도 있나요?

네, 외부 명령어 실행 파일의 경로가 잘못되었거나 실행 권한이 없는 경우에도 `subprocess` 관련 문제가 발생할 수 있습니다. 파이썬이 해당 실행 파일을 찾지 못하면 `FileNotFoundError`와 같은 예외가 발생해야 하지만, 특정 환경이나 설정에서는 오류가 명확히 표시되지 않고 프로세스가 멈추는 것처럼 보일 수 있습니다. 또한, 실행 파일은 존재하지만 해당 사용자에게 실행 권한이 없을 때도 정상적으로 실행되지 못하고 멈출 수 있습니다. 명령어가 올바른 경로에 있는지, 그리고 실행 권한이 있는지 먼저 확인해보는 것이 좋습니다.




Q. `check=True` 옵션을 사용했는데, 자식 프로세스가 오류 코드를 반환하여 멈춘 건가요?

`subprocess.run()` 함수에 `check=True` 옵션을 설정하면, 자식 프로세스가 0이 아닌 종료 코드를 반환할 경우 `CalledProcessError` 예외가 발생합니다. 이 예외가 발생하면 프로그램 실행이 중단됩니다. 따라서 `check=True` 때문에 멈춘 것처럼 보이는 것은, 실제로 자식 프로세스가 오류를 반환했기 때문입니다. 오류를 확인하고 싶을 때 유용한 옵션이지만, 모든 오류에 대해 예외 처리를 해주는 것이 좋습니다.




Q. 입력(stdin)을 제공해야 하는 외부 프로그램을 subprocess로 실행할 때, 입력 제공 방식에 문제가 있으면 멈추나요?

네, 입력(stdin)을 요구하는 외부 프로그램을 `subprocess`로 실행할 때 입력 제공 방식이 잘못되면 멈출 수 있습니다. 만약 프로그램이 입력을 기다리는데 `stdin`을 제대로 설정하지 않거나, 제공하는 입력 데이터가 부족하거나 형식이 잘못된 경우, 자식 프로세스는 입력을 받지 못해 무한히 대기하게 됩니다. 이럴 때는 `stdin` 인자를 통해 정확한 데이터를 문자열이나 바이트 형태로 제공해야 하며, `text=True` (또는 `encoding`) 옵션을 함께 사용하여 문자열 인코딩을 관리하는 것이 중요합니다.




Q. 여러 subprocess를 동시에 실행하거나, subprocess와 파이썬 스크립트 간의 통신에서 데드락(Deadlock)이 발생할 가능성이 있나요?

데드락은 여러 프로세스나 스레드가 서로 자원을 기다리면서 영원히 멈추는 현상입니다. `subprocess`를 사용할 때, 특히 양방향 통신(예: subprocess의 stdout을 읽으면서 동시에 stdin으로 데이터를 보내는 경우)을 구현할 때 데드락이 발생할 가능성이 있습니다. 예를 들어, 파이썬이 subprocess의 stdout을 모두 읽지 않은 상태에서 stdin으로 계속 데이터를 보내면, subprocess의 내부 버퍼가 꽉 차서 더 이상 동작하지 못하고 대기하게 됩니다. 이를 방지하려면 `Popen` 객체를 사용하고, `stdout.read()`와 `stdin.write()` 등의 메서드를 호출할 때 논블로킹(non-blocking) 방식을 사용하거나, 별도의 스레드를 활용하여 통신을 관리하는 것이 좋습니다.




Q. subprocess 실행 후, 완료된 프로세스의 출력을 확인하거나 디버깅하는 데 사용할 수 있는 방법은 무엇인가요?

subprocess가 멈추는 문제를 디버깅하려면, 자식 프로세스가 생성하는 표준 출력(stdout)과 표준 오류(stderr)를 꼼꼼히 확인하는 것이 중요합니다. `subprocess.run()` 함수에서 `capture_output=True` 옵션을 사용하면 stdout과 stderr을 캡처할 수 있습니다. 캡처된 출력은 `.stdout`과 `.stderr` 속성으로 접근 가능하며, 이 내용을 확인하면 어떤 오류 메시지가 발생했는지, 또는 프로그램이 어디서 멈췄는지 단서를 얻을 수 있습니다. 또한, `text=True` 옵션을 함께 사용하면 출력을 문자열 형태로 받아보기 편리합니다. 만약 멈추는 시점을 정확히 알고 싶다면, `print()` 문을 삽입하여 코드 실행 흐름을 추적하는 것도 유용한 방법입니다.

somewhere.
@somewhere.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차