Python 병렬 처리 완벽 가이드 | 효율적 구현법과 활용 예시

1. 소개

Python에서 병렬 처리의 중요성

Python은 단순하고 사용하기 쉬운 프로그래밍 언어로서, 다양한 용도로 활용되고 있습니다. 하지만 복잡한 데이터 처리나 연산이 필요한 경우, Python의 처리 속도가 때로는 문제가 되기도 합니다. 이를 해결하기 위해 여러 작업을 동시에 실행할 수 있는 “병렬 처리”가 중요한 역할을 합니다. 이 글에서는 Python에서 병렬 처리를 어떻게 구현할 수 있는지, 기본적인 방법부터 구체적인 활용 사례까지 소개합니다.

2. Python에서의 병렬 처리 방법

병렬 처리의 주요 방법

Python에는 병렬 처리를 구현하는 여러 가지 방법이 있습니다. 주요한 것은 다음 세 가지입니다.
  1. 멀티스레드 (threading 모듈) 여러 스레드를 사용해 작업을 병행 실행하지만, Python의 GIL(Global Interpreter Lock)의 영향으로 CPU를 많이 사용하는 작업에서는 효과가 제한적입니다.
  2. 멀티프로세스 (multiprocessing 모듈) 각 프로세스가 독립된 메모리 공간을 갖기 때문에 GIL의 영향을 받지 않아 진정한 병렬 처리가 가능합니다. 대규모 데이터 처리나 무거운 계산에 적합합니다.
  3. 비동기 처리 (asyncio 모듈) 비동기 처리는 I/O 바운드 작업(네트워크 통신이나 파일 작업 등)에 효과적입니다. 이를 통해 대기 시간이 많은 처리를 효율적으로 진행할 수 있습니다.
侍エンジニア塾

3. 멀티프로세스 vs 멀티스레드

GIL(Global Interpreter Lock)의 영향

Python에는 GIL이라고 불리는 메커니즘이 있어 한 번에 하나의 스레드만 실행될 수 있습니다. 이 때문에 CPU 바운드 작업에서는 스레드를 늘려도 성능이 향상되지 않습니다. 따라서 스레드를 이용한 병렬 처리는 대기 시간이 많은 I/O 바운드 작업에서만 효과적입니다.

멀티스레드의 장점과 한계

스레드는 경량이며 I/O 바운드 작업(파일 조작이나 네트워크 처리 등)에 최적입니다. 그러나 앞서 언급한 GIL 때문에 여러 CPU 코어를 온전히 활용할 수 없어서 CPU 바운드 작업에는 적합하지 않습니다.
import threading
import time

def worker(num):
print(f"Worker {num} starting")
time.sleep(2)
print(f"Worker {num} finished")

threads = []
for i in range(5):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()

for t in threads:
t.join()
이 코드는 5개의 스레드를 동시에 실행하며, 각 스레드는 2초 동안 슬립한 뒤 종료합니다. 멀티스레드를 사용하면 작업이 병행해서 진행되는 모습을 확인할 수 있습니다.

멀티프로세스의 장점

GIL의 제약을 회피하려면 멀티프로세스가 효과적입니다. 프로세스는 스레드와 달리 독립된 메모리 공간을 가지므로 여러 CPU 코어를 온전히 활용할 수 있습니다. 특히 무거운 계산 처리나 대규모 데이터를 다루는 경우에 효과를 발휘합니다.
from multiprocessing import Process
import time

def worker(num):
print(f"Worker {num} starting")
time.sleep(2)
print(f"Worker {num} finished")

if name == 'main':
processes = []
for i in range(5):
p = Process(target=worker, args=(i,))
processes.append(p)
p.start()

for p in processes:
    p.join()
이 예에서는 5개의 프로세스가 병행해서 동작하며, 각각 독립적으로 작업을 실행합니다。join()메서드는 각 프로세스가 종료될 때까지 대기하므로, 모든 프로세스가 완료될 때까지 프로그램은 다음 단계로 진행하지 않습니다。

4. Python에서 병렬 처리를 구현하는 방법

multiprocessing 모듈을 이용한 병렬 처리

multiprocessing 모듈을 사용하면 여러 프로세스를 효율적으로 관리할 수 있습니다. 아래는 프로세스 풀을 사용해 작업을 병렬로 처리하는 기본 예시입니다.
from multiprocessing import Pool

def square(x):
return x * x

if name == 'main':
with Pool(4) as p:
result = p.map(square, [1, 2, 3, 4, 5])
print(result)
이 코드에서는 4개의 프로세스가 동시에 실행되어 각각 리스트의 요소에 대해 제곱 계산을 수행합니다. 결과는 리스트로 반환되며, 병렬 처리의 효율을 확인할 수 있습니다.
年収訴求

5. 비동기 처리와 그 용도

asyncio 모듈을 사용한 비동기 처리

asyncio는 I/O 대기 시간이 발생하는 작업에 특히 적합합니다. 네트워크 통신이나 파일 입출력과 같은 처리를 대기 시간 중에 다른 작업을 병행 처리함으로써 효율적으로 진행할 수 있습니다.
import asyncio

async def worker(num):
print(f'Worker {num} starting')
await asyncio.sleep(1)
print(f'Worker {num} finished')

async def main():
tasks = [worker(i) for i in range(5)]
await asyncio.gather(*tasks)

asyncio.run(main())
이 코드는 5개의 작업을 병행하여 처리합니다. await를 사용하면 비동기 처리가 이루어져, 각 작업의 대기 시간 동안 다른 작업이 실행됩니다.

6. 병렬 처리의 성능 튜닝

Joblib을 사용한 병렬화

Joblib은 데이터 처리나 머신러닝 모델 학습 등 무거운 연산을 효율적으로 수행하기 위한 라이브러리입니다. 아래 코드는 Joblib을 사용해 병렬 처리를 수행하는 예입니다.
from joblib import Parallel, delayed

def heavy_task(n):
return n ** 2

results = Parallel(n_jobs=4)(delayed(heavy_task)(i) for i in range(10))
print(results)
n_jobs를 지정하면 동시에 실행할 프로세스 수를 제어할 수 있습니다. 이 예에서는 4개의 프로세스로 계산을 병렬로 수행하고, 결과를 리스트로 반환합니다.

7. Python 병렬 처리의 실용적 활용 사례

데이터 처리와 웹 스크레이핑

Python의 병렬 처리는 데이터 처리나 웹 스크레이핑처럼 많은 데이터를 동시에 다루는 상황에서 특히 효과적입니다. 예를 들어 웹 페이지를 크롤링할 때 멀티스레딩이나 비동기 처리를 사용하면 동시에 여러 요청을 보낼 수 있어 처리 시간을 크게 단축할 수 있습니다. 또한 기계 학습의 학습 단계나 데이터 전처리에서도 multiprocessingJoblib을 활용해 성능을 향상시킬 수 있습니다.

8. 정리

병렬 처리는 Python의 성능을 최대한 끌어내기 위해 빼놓을 수 없는 기술입니다. threading, multiprocessing, asyncio, 그리고 Joblib 등의 모듈을 적절히 구분해 사용하면 다양한 상황에서 작업을 효율적으로 처리할 수 있습니다. 실제 프로젝트에서 이러한 기술을 활용해 처리 효율화를 목표로 합시다.
侍エンジニア塾