multiprocessing 기초

Python multiprocessing 모듈로 별도 프로세스를 생성하는 방법, Queue와 Pipe로 프로세스 간 통신(IPC), Pool.map 기초, Windows/Linux 차이점을 설명합니다.

· 4 min read · PALDYN Team

지난 글에서 threading 모듈로 스레드를 다루는 방법을 살펴봤다. CPU 바운드 작업을 진짜로 병렬 실행하려면 GIL을 피해야 한다. multiprocessing 모듈은 별도의 Python 프로세스를 생성해 각 프로세스가 독립된 GIL을 갖게 하므로 멀티코어를 온전히 활용할 수 있다.

threading vs multiprocessing

멀티스레딩은 하나의 프로세스 안에서 메모리를 공유하며 동작하고, 멀티프로세싱은 별도 프로세스를 생성해 메모리를 독립적으로 운영한다.

멀티스레딩 vs 멀티프로세싱

Process 기본 사용법

from multiprocessing import Process

def worker(name):
    print(f"{name}: 작업 시작")
    result = sum(i * i for i in range(10_000_000))
    print(f"{name}: 결과 = {result}")

if __name__ == "__main__":
    p = Process(target=worker, args=("P1",))
    p.start()   # 자식 프로세스 시작
    p.join()    # 완료 대기

if __name__ == "__main__": 블록이 필수다. 없으면 Windows에서 자식 프로세스가 모듈을 임포트할 때 다시 Process를 생성하는 무한 루프가 발생한다.

Queue — 프로세스 간 통신(IPC)

프로세스는 메모리를 공유하지 않는다. 결과를 전달하려면 Queue, Pipe, Manager 같은 IPC 수단이 필요하다.

from multiprocessing import Process, Queue

def compute(n, q):
    result = sum(i * i for i in range(n))
    q.put(result)   # 결과를 큐에 넣기

if __name__ == "__main__":
    q = Queue()
    p = Process(target=compute, args=(10_000_000, q))
    p.start()
    result = q.get()   # 자식 프로세스 결과 수신
    p.join()
    print(result)

multiprocessing 기본 예제

Pipe — 양방향 통신

Pipe()는 두 개의 연결 엔드를 반환한다. 부모-자식 간 양방향 통신에 쓴다.

from multiprocessing import Process, Pipe

def child_proc(conn):
    msg = conn.recv()         # 부모에서 수신
    conn.send(msg.upper())    # 응답 전송
    conn.close()

if __name__ == "__main__":
    parent_conn, child_conn = Pipe()
    p = Process(target=child_proc, args=(child_conn,))
    p.start()
    parent_conn.send("hello")       # 자식에게 전송
    print(parent_conn.recv())       # 자식 응답 수신 → "HELLO"
    p.join()

Pool — 간단한 병렬 실행

Pool은 프로세스 풀을 관리하고 작업을 분배하는 고수준 API다. map()으로 이터러블의 각 원소에 함수를 병렬 적용한다.

from multiprocessing import Pool

def square(n):
    return n * n

if __name__ == "__main__":
    with Pool(processes=4) as pool:
        results = pool.map(square, range(10))
    print(results)
    # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Pool은 내부에서 프로세스를 재사용하므로 매번 Process를 생성하는 것보다 효율적이다.

공유 메모리

ValueArray로 프로세스 간 공유 메모리를 만들 수 있다.

from multiprocessing import Process, Value

def add_100(n):
    n.value += 100

if __name__ == "__main__":
    shared = Value("i", 0)   # 'i' = int
    procs = [Process(target=add_100, args=(shared,)) for _ in range(4)]
    for p in procs: p.start()
    for p in procs: p.join()
    print(shared.value)  # 400 (경쟁 조건 주의 — Lock 필요할 수 있음)

시작 방법: spawn vs fork

multiprocessing은 세 가지 시작 방식을 지원한다.

방식설명기본 OS
spawn새 Python 인터프리터 시작Windows, macOS (3.8+)
fork부모 프로세스 복사Linux
forkserver전용 서버 프로세스 경유Unix

fork는 빠르지만 멀티스레드 환경에서 불안정할 수 있다. spawn은 느리지만 안전하다.

import multiprocessing as mp

mp.set_start_method("spawn")  # 시작 방법 명시적 설정

요약

  • Process(target, args) → 별도 프로세스로 함수 실행
  • Queue / Pipe → 프로세스 간 데이터 전달
  • Pool.map(fn, iterable) → 고수준 병렬 맵
  • Value / Array → 공유 메모리
  • CPU 바운드는 multiprocessing, I/O 바운드는 threading / asyncio

지난 글: threading 모듈

다음 글: ProcessPoolExecutor


읽어주셔서 감사합니다. 😊