본문 바로가기

Python

[Python] Multiprocessing와 subprocess의 차이

2024.08.28 TIL

요즘 파이썬으로 DL 모델의 inference 서버를 만들고 있다. main.py에서 다른 스크립트를 subprocess로 실행시키도록 코드를 짰었는데, 문득 multiprocessingsubprocess가 어떤 차이인지, 나의 경우 무엇을 사용하는 것이 적합한지 알아보고 변경해야겠다는 생각이 들었다.

multiprocessingsubprocess는 모두 Python에서 병렬 처리를 다룰 때 사용하는 모듈이다. 즉, 프로세스를 생성하고 독립적으로 실행될 수 있도록 지원하는 것이다. 두 모듈 모두 새로운 프로세스를 생성하며, 그 말은 곧 부모 프로세스와 별도의 메모리 공간을 가진다는 것이다.

  1. 사용 목적
    • multiprocessing
      1. Python 코드 내부에서 병렬 처리를 수행할 때 사용한다.
      2. 여러 Python 함수 및 메서드를 병렬로 실행하거나, 데이터 병렬성을 처리할 때 주로 사용된다.
      3. CPU의 집약적인 작업이나 GIL(Global Interpreter Lock)을 피하고자 할 때 유용하다.
        • GIL(Global Interpreter Lock)이란?
          • Python 인터프리터에서 하나의 스레드만이 한 번에 Python 바이트코드를 실행할 수 있도록 하는 메커니즘이다. 즉, 멀티스레딩 환경에서도 사실상 하나의 스레드만이 Python 코드를 실행하게 만든다는 것을 의미한다. (다음에 GIL에 대해서도 공부를 하고 글을 써보도록 하겠다…)
    • subprocess
      1. 외부 프로그램, 명령어 또는 다른 스크립트를 실행할 때 사용한다.
      2. Python 외부의 작업을 처리하고, 결과를 Python으로 가져와야 할 때 사용한다.
      3. 운영체제의 명령어나 외부의 독립적인 프로그램을 실행할 때 사용한다.
      즉, multiprocessing은 Python 스크립트를 프로세스로 실행하고 싶을 때 사용하면 되고, subprocess는 Python으로 쓰이지 않은 다른 프로그램이나 외부 프로그램 혹은 운영체제 명령어 등을 이용하고 싶을 때 사용하면 된다.
  2. 프로세스 간 통신
    • multiprocessing
      1. multiprocessing.Queue, Pipe, Manager과 같은 데이터 구조를 이용할 수 있다.
      2. Python 프로세스 간에 객체나 메모리 상의 데이터 구조를 공유할 수 있다.
    • subprocess
      1. 프로세스 간의 통신은 표준 입출력(standard input/output) 스트림을 통해 이루어진다.
      2. subprocess.Popen을 사용하여 표준 입출력, 오류 스트림을 캡쳐할 수 있다.
      3. Python과 외부 프로그램 간의 간단한 데이터를 주고받기에 적합하다.
  3. 데이터 공유
    • multiprocessing
      1. 부모 프로세스와 자식 프로세스 간에 데이터를 안전하게 공유할 수 있는 메커니즘을 제공한다.
      2. ValueArray 같은 공유 메모리 구조를 사용하여 데이터 공유가 가능하다.
    • subprocess
      1. 데이터 공유를 위한 특별한 메커니즘이 없다.
  4. OS 의존성
    • multiprocessing
      1. 플랫폼에 따라 프로세스 생성 방식이 다를 수 있다. Unix 계열에서는 fork를 사용하고 Windows에서는 spawn을 사용한다.
    • subprocess
      1. 모든 주요 운영체제에서 사용할 수 있으며 운영체제의 기본 쉘을 통해 명령어를 실행한다.

요약하면, multiprocessing은 Python 내부에서 병렬 작업을 위한 도구로, 프로세스 간에 데이터를 공유하거나 병렬 작업을 수행할 때 유용하다. 반면, subprocess는 외부 프로그램이나 명령어를 실행하고, 이와 관련된 입출력 처리를 할 때 적합하다.

 

 

그렇다면 둘 다 사용 가능한 Python 스크립트를 실행하는 시나리오에서는 어떤 것을 선택해야 할까?

작업이 Python 내부에서 수행되고, GIL 우회나 복잡한 데이터 공유가 필요하다면 multiprocessing, 작업이 외부 프로그램에 위임될 수 있고, Python은 단순히 이를 실행하고 결과를 받아오면 된다면 subprocess를 사용하면 된다.

 

나의 경우에는 main.py에서 script A, B, C, D를 실행해야 하는데 4개 모두 Python 스크립트이며, 그 중 A와 B에서는 서로 프로세스 간 통신이 이루어져야 하므로 multiprocessing을 택했다.

아래는 이전 코드와 바뀐 코드이다. (일부 변수는 간단하게 바꿨다.)

(이전 코드)

import multiprocessing
import subprocess

def run_script(script_name, queue=None):
    if queue:
        subprocess.run(["python", script_name, queue])
    else:
        subprocess.run(["python", script_name])

if __name__ == "__main__":
    # multiprocessing.Queue 생성
    shared_queue = multiprocessing.Queue()

    # 스크립트 이름 리스트
    scripts = [
        "app/A.py",
        "app/B.py",
        "app/C.py",
        "app/D.py"
    ]

    # 각 스크립트를 subprocess로 실행
    processes = []
    for script in scripts:
        if script in ["app/A.py", "app/B.py"]:
            # queue를 사용하는 스크립트
            p = multiprocessing.Process(target=run_script, args=(script, shared_queue))
        else:
            # queue를 사용하지 않는 스크립트
            p = multiprocessing.Process(target=run_script, args=(script,))

        p.start()
        processes.append(p)

    # 모든 프로세스가 종료될 때까지 대기
    for p in processes:
        p.join()

(바뀐 코드)

import multiprocessing
from A import A
from B import B
from C import C
from D import D

if __name__ == "__main__":
    # 큐 생성
    shared_queue = multiprocessing.Queue()

    # 프로세스 생성
    processes = [
        multiprocessing.Process(target=A, args=(shared_queue,)),
        multiprocessing.Process(target=B, args=(shared_queue,)),
        multiprocessing.Process(target=C),
        multiprocessing.Process(target=D),
    ]

    # 프로세스 시작
    for process in processes:
        process.start()

    # 프로세스 종료 대기
    for process in processes:
        process.join()