현대 프로그래밍에서 병행(Concurrency) 및 병렬(Parallelism) 처리는 애플리케이션의 성능을 최적화하고 CPU 자원을 효율적으로 활용하는 중요한 기술입니다. Python에서는 multiprocessing
모듈을 사용하여 여러 개의 프로세스를 실행하고, GIL(Global Interpreter Lock) 의 영향을 받지 않는 진정한 병렬 처리를 구현할 수 있습니다.
이 글에서는 병행성과 병렬성의 차이, 멀티프로세싱 개념, Python에서의 구현 방법, 공유 메모리 문제 해결, 그리고 성능 최적화 기법까지 깊이 있게 다뤄보겠습니다.
1. 병행성과 병렬성: 개념과 차이점
🔹 병행성(Concurrency)란?
- 여러 작업이 동시에 진행되는 것처럼 보이는 개념입니다.
- 실제로는 하나의 CPU가 여러 작업을 빠르게 전환하며 실행(시분할 방식)합니다.
- Python의 멀티스레딩(Threading) 은 병행성 구현에 적합합니다.
- 주로 I/O 바운드 작업(네트워크 요청, 파일 입출력 등) 에서 효과적입니다.
🔹 병렬성(Parallelism)란?
- 실제로 여러 작업이 동시에 실행되는 개념입니다.
- 여러 개의 CPU 코어가 각각 다른 작업을 동시에 수행합니다.
- Python의 멀티프로세싱(Multiprocessing) 을 활용하면 GIL의 영향을 받지 않고 병렬 처리를 구현할 수 있습니다.
- CPU 바운드 작업(복잡한 연산, 이미지 처리 등) 에서 성능을 극대화할 수 있습니다.
🔹 병행성과 병렬성 비교
개념 | 병행성 (Concurrency) | 병렬성 (Parallelism) |
---|---|---|
실행 방식 | 여러 작업을 빠르게 전환하며 실행 | 여러 작업을 동시에 실행 |
CPU 활용 | 단일 CPU에서 작업 전환 | 여러 CPU에서 작업 분배 |
GIL 영향 | Python GIL의 영향을 받음 | GIL의 영향을 받지 않음 |
주요 기법 | 멀티스레딩(Threading) | 멀티프로세싱(Multiprocessing) |
적합한 작업 | I/O 바운드 | CPU 바운드 |
2. Python 멀티프로세싱 개요
🔹 멀티프로세싱이란?
멀티프로세싱(Multiprocessing)은 여러 개의 프로세스를 생성하여 CPU의 여러 코어를 활용할 수 있도록 해주는 기법입니다. Python의 multiprocessing
모듈을 사용하면 여러 개의 프로세스를 실행하여 GIL의 영향을 받지 않고 병렬 처리를 수행할 수 있습니다.
🔹 멀티프로세싱의 주요 특징
- 독립된 메모리 공간 사용: 각 프로세스는 별도의 메모리 공간을 가짐.
- GIL(Global Interpreter Lock) 문제 없음: 각 프로세스가 독립적으로 실행되므로, Python의 GIL 영향을 받지 않음.
- CPU 집약적인 작업에 적합: 수학적 연산, 이미지 처리, 머신 러닝 모델 훈련 등.
3. Python에서 멀티프로세싱 구현
🔹 기본적인 멀티프로세싱 예제
import multiprocessing
import time
def worker(num):
print(f'Worker {num} 시작')
time.sleep(2) # 작업 수행
print(f'Worker {num} 종료')
if __name__ == '__main__':
processes = []
for i in range(5): # 5개의 프로세스 생성
p = multiprocessing.Process(target=worker, args=(i,))
processes.append(p)
p.start()
for p in processes:
p.join() # 모든 프로세스가 종료될 때까지 대기
print("모든 작업 완료")
✅ 실행 결과:
Worker 0 시작
Worker 1 시작
Worker 2 시작
Worker 3 시작
Worker 4 시작
Worker 0 종료
Worker 1 종료
Worker 2 종료
Worker 3 종료
Worker 4 종료
모든 작업 완료
위 코드는 worker
함수를 실행하는 5개의 독립된 프로세스를 생성하며, join()
을 사용하여 모든 프로세스가 종료될 때까지 기다립니다.
4. 멀티프로세싱에서 데이터 공유
멀티프로세싱 환경에서는 각 프로세스가 독립적인 메모리 공간을 사용하므로, 데이터를 공유하려면 Queue
또는 Pipe
를 활용해야 합니다.
🔹 큐(Queue)를 활용한 데이터 공유
from multiprocessing import Process, Queue
def square_numbers(queue):
for i in range(10):
queue.put(i * i) # 데이터를 큐에 추가
if __name__ == '__main__':
queue = Queue()
process = Process(target=square_numbers, args=(queue,))
process.start()
process.join()
while not queue.empty():
print(queue.get()) # 큐에서 데이터 가져오기
✅ 실행 결과:
0
1
4
9
16
25
36
49
64
81
이 코드는 프로세스 간 데이터를 교환하기 위해 Queue
를 사용하며, 여러 개의 프로세스에서 데이터를 안전하게 주고받을 수 있습니다.
5. 멀티프로세싱 Pool을 활용한 병렬 처리
여러 개의 작업을 병렬로 실행해야 할 때, Pool
클래스를 사용하면 더 간결한 코드로 병렬 프로세스를 실행할 수 있습니다.
🔹 Pool
을 활용한 병렬 실행
from multiprocessing import Pool
def cube(x):
return x * x * x
if __name__ == '__main__':
with Pool(processes=4) as pool: # 4개의 프로세스 실행
results = pool.map(cube, range(10))
print(results) # [0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
✅ 실행 결과:
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
이 예제에서는 map()
을 사용하여 리스트의 각 요소를 병렬로 처리하며, 최대 4개의 프로세스를 활용하여 연산 속도를 최적화합니다.
6. 멀티프로세싱 활용 시 고려할 점
✅ 멀티프로세싱이 적합한 경우
- CPU 바운드 작업(수학적 연산, 머신 러닝 훈련, 이미지 및 비디오 처리)
- GIL 문제를 회피해야 하는 경우
- 대용량 데이터 처리(빅데이터 분석, 병렬 DB 쿼리 실행)
❌ 멀티프로세싱이 비효율적인 경우
- I/O 바운드 작업(네트워크 요청, 파일 입출력) →
threading
이 더 적합함. - 프로세스 간 통신이 잦은 경우 → 데이터 공유 비용이 증가하여 성능 저하 발생.
🔥 결론: 멀티프로세싱을 활용한 병렬 처리 최적화
Python의 multiprocessing
모듈을 활용하면 CPU의 여러 코어를 최대한 활용하여 연산 속도를 극대화할 수 있습니다. 멀티프로세싱을 적절히 활용하면 GIL의 제약을 우회하고, CPU 바운드 작업을 효율적으로 수행할 수 있습니다.
🎯 핵심 정리
- 병행성과 병렬성의 차이를 이해하고, 작업 유형에 따라 적절한 방법을 선택해야 함.
- 멀티프로세싱은 CPU 바운드 작업에 적합하며, GIL 문제를 해결할 수 있음.
Process
,Queue
,Pool
등의 다양한 API를 활용하면 더욱 최적화된 병렬 처리가 가능.
멀티프로세싱을 제대로 활용하면 Python 프로그램의 성능을 한 단계 더 끌어올릴 수 있습니다. 🚀
'프로그래밍 > Python' 카테고리의 다른 글
SQLite 완벽 가이드: Python과 함께하는 경량 데이터베이스 활용법 (0) | 2025.02.27 |
---|---|
병행 및 병렬 처리: Python의 async/await 완벽 가이드 (0) | 2025.02.27 |
병행 및 병렬 처리: Python 스레딩 완벽 가이드 (0) | 2025.02.27 |
파이썬 예외 전파 완벽 가이드: 안정적인 코드 작성을 위한 예외 처리 기법 (0) | 2025.02.27 |
파이썬 사용자 정의 예외 완벽 가이드: 효율적인 예외 처리 기법 (0) | 2025.02.27 |