🚀 multiprocessing으로 CPU 병렬 처리 쉽게 구현하기
multiprocessing 모듈은 멀티코어 CPU를 활용한 병렬 프로세싱을 가능하게 해 줍니다. 스레드와는 달리 **GIL의 제약 없이** 완전한 병렬 처리를 수행할 수 있어 CPU 바운드 작업에 최적입니다.
✅ 1. `Process`로 직접 프로세스 생성하기
from multiprocessing import Process
import os
def worker(num):
print(f"프로세스 {num} 시작, PID={os.getpid()}")
if __name__ == '__main__':
procs = [Process(target=worker, args=(i,)) for i in range(4)]
for p in procs:
p.start()
for p in procs:
p.join()
- 각 `Process`는 독립된 메모리 공간을 가지고 실행됩니다.
- `join()`을 통해 각 프로세스 종료를 기다릴 수 있습니다.
✅ 2. `Pool`을 활용한 작업 분산 처리
from multiprocessing import Pool
def fib(n):
return n if n < 2 else fib(n-1) + fib(n-2)
if __name__ == '__main__':
with Pool(processes=4) as pool:
results = pool.map(fib, [20, 22, 24, 26])
print(results)
- `Pool.map()`은 입력 값을 자동으로 분할해 병렬 계산합니다.
- CPU 병렬화로 재귀 계산이 빠르게 완료됩니다.
✅ 3. `Queue`로 서로 다른 프로세스 간 통신하기
from multiprocessing import Process, Queue
def producer(q):
for val in ['A','B','C']:
q.put(val)
q.put(None)
def consumer(q):
while True:
item = q.get()
if item is None:
break
print("소비:", item)
if __name__ == '__main__':
q = Queue()
p1 = Process(target=producer, args=(q,))
p2 = Process(target=consumer, args=(q,))
p1.start(); p2.start()
p1.join(); p2.join()
- 프로세스 간 안전한 데이터 전달, `None`과 같은 종료 시그널 사용 권장.
- Queue는 내부적으로 립-싱크(lock) 처리를 자동 수행합니다.
✅ 4. `Manager()`로 공유 메모리 값 다루기
from multiprocessing import Manager, Process
def worker(d, l, key, value):
d[key] = value
l.append(key)
if __name__ == '__main__':
mgr = Manager()
shared_dict = mgr.dict()
shared_list = mgr.list()
procs = [Process(target=worker, args=(shared_dict, shared_list, i, i*10)) for i in range(5)]
for p in procs:
p.start()
for p in procs:
p.join()
print(shared_list)
print(shared_dict)
- 복수 값 공유 및 동시 수정이 필요할 때 유용.
- 하지만 느릴 수 있으므로 빈번한 접근은 피하세요.
✅ 5. 성능 비교: `map` vs `Pool` vs 단일 함수
import timeit, multiprocessing
setup = """
from multiprocessing import Pool
def heavy(x):
s = 0
for i in range(1000000):
s += i*x
return s
data = range(4)
"""
t_single = timeit.timeit("list(map(heavy, data))", setup=setup, number=10)
t_pool = timeit.timeit("""
with Pool(processes=4) as p:
p.map(heavy, data)
""", setup=setup + "\nfrom multiprocessing import Pool", number=10)
print("단일 프로세스:", t_single)
print("Pool 병렬 처리:", t_pool)
- CPU 바운드 작업 시 `Pool` 사용이 압도적으로 빠름!
- 작업 단위가 작을 경우 오버헤드가 커질 수 있으니 **적절한 블록 사이즈** 고려하세요.
✨ 요약 & 활용 팁
- GIL 제약 없이 진정한 병렬 처리 가능 → CPU 바운드 작업에 적합
- 작업 자동 분배가 필요한 경우 `Pool`이 가장 쉽고 효과적
- 프로세스 간 통신은 `Queue`, `Manager`로 안전하게 수행할 수 있음
- 데이터 공유 구조는 편하지만 성능 저하 고려 필요
- 작업 크기와 프로세스 개수는 벤치마크로 최적화하세요
댓글
댓글 쓰기