본문 바로가기
C.W.K.
Stream
Lesson 04 of 05 · published

프로세스 — CPU 작업을 여러 코어에 나누기

~18 min · multiprocessing, process, parallel, executor

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

프로세스마다 인터프리터와 GIL, 메모리가 따로 있어 CPU 작업을 실제 병렬로 돌릴 수 있어. 대신 시작과 pickle 직렬화 비용이 크므로 작업이 충분히 무거워야 이겨.

ProcessPoolExecutor는 스레드 풀과 같은 API를 쓰지만 함수는 최상위에 있고 인자·결과가 pickle 가능해야 해. 메인 가드도 필요해. 큰 객체를 매 작업에 복사하지 말고 작은 입력, 충분히 큰 계산, 필요한 결과만 돌려줘. 공유 Manager나 shared_memory는 정말 공유가 필요한 경우에만 써.

Code

ProcessPoolExecutor로 CPU 작업 나누기·python
from concurrent.futures import ProcessPoolExecutor
import time

def compute_squares(n):
    return sum(i * i for i in range(n))

if __name__ == "__main__":           # 일부 플랫폼에서 multiprocessing 에 필수
    start = time.perf_counter()
    with ProcessPoolExecutor(max_workers=4) as pool:
        results = list(pool.map(compute_squares, [1_000_000] * 4))
    print(f"결과 개수: {len(results)}")
    print(f"경과: {time.perf_counter() - start:.2f}s")
# 다중 코어 머신에서 4 프로세스로 단일 프로세스나
# (GIL 때문에) ThreadPoolExecutor 의 같은 거보다 ~4x 빠름
메인 가드가 필요한 이유·python
# Windows 와 macOS-spawn 에서 multiprocessing 이 각 worker 에서
# 스크립트 import. if __name__ guard 없으면 자식 worker 가
# multiprocessing setup 재실행 + 재귀 spawn.
#
# 항상 top-level multiprocessing 코드 guard:
#
# def work(x):
#     return x * 2
#
# if __name__ == '__main__':
#     with ProcessPoolExecutor() as pool:
#         results = list(pool.map(work, range(10)))
#         print(results)

# 함수가 picklable 해야 — 모듈 top level 정의
pickle할 수 있는 함수와 값·python
from concurrent.futures import ProcessPoolExecutor

# 작동 — 모듈 레벨 함수, picklable 인자
def double(x):
    return x * 2

# 작동하지 않음 — lambda 가 picklable 아님
# squarer = lambda x: x * x
# pool.submit(squarer, 5)         # PicklingError

# 우회 — 모듈 레벨에 함수 정의, 또는 functools.partial 사용
# import functools
# def multiply(a, b):
#     return a * b
# squarer = functools.partial(multiply, 2)   # picklable

if __name__ == '__main__':
    with ProcessPoolExecutor() as pool:
        results = list(pool.map(double, [1, 2, 3, 4]))
        print(results)                # [2, 4, 6, 8]
프로세스를 쓰지 않는 편이 빠른 경우·python
# 나쁜 use case — 오버헤드 지배
#
# 1. 작은 task. pickle + IPC + 프로세스 시작 오버헤드가
#    작업 자체 훨씬 초과.
#
# 2. I/O 바운드 작업. asyncio 또는 threading 이 훨씬 적은 오버헤드로.
#
# 3. 공유 state 필요한 어디든. multiprocessing 이 그거 어렵게 함.
#    asyncio + lock, 또는 공유 state 없이 디자인.
#
# 좋은 use case:
# 1. 병렬화 가능한 숫자 작업 — ML 전처리, 이미지 처리,
#    과학 시뮬레이션
# 2. 각 >>100ms 인 독립 CPU 무거운 task
# 3. 진짜 OS 레벨 병렬성 필요 (threading 과 다름)

# 오늘의 편향 — I/O 엔 asyncio 먼저 시도. multiprocessing 은
# CPU 바운드 병렬 작업에 특정해서.

External links

Exercise

캐시 없는 재귀 fib로 35, 36, 37, 38을 ProcessPoolExecutor에서 함께 계산하고 순차 실행과 시간을 비교해. 메인 가드를 반드시 둬.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.