프로그래밍/Python

병행 및 병렬 처리: Python 멀티프로세싱 완벽 가이드

shimdh 2025. 2. 27. 10:13
728x90

현대 프로그래밍에서 병행(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 프로그램의 성능을 한 단계 더 끌어올릴 수 있습니다. 🚀

728x90