[Python] 파이썬 멀티프로세싱과 멀티스레딩
병렬처리(Parallel Processing)
프로그래밍을 하다보면 성능을 위해 병렬처리가 필요한 경우가 생깁니다.
병렬 처리는 여러 작업을 동시에 처리하는 컴퓨팅 방식을 의미합니다. 병렬 처리 프로그래밍으로 하나의 작업을 여러 부분으로 나누어 동시에 처리하므로 전체 작업을 보다 빠르게 완료할 수 있습니다.
이 병렬 처리를 구현하기 위해 접하게 되는 개념이 멀티프로세싱과 멀티스레딩인데요. 두 개념이 어떻게 다른지 알아보도록 하겠습니다.
멀티프로세싱(Multiprocessing)과 멀티스레딩(Multithreading)
멀티프로세싱은 병렬 처리를 위해 여러 개의 독립적인 프로세스를 사용하는 것을 뜻합니다.
각 프로세스는 자체 메모리 공간을 가지고 별도의 주소 공간에서 실행됩니다. 멀티 프로세싱은 병렬 처리를 위해 동시에 여러 프로세스를 실행시키는 것으로 여러 CPU 또는 코어를 사용하여 작업을 분할하여 처리합니다.
이렇게 분할된 작업은 독립적으로 실행되므로 안정성과 신뢰성이 높아집니다. 또 여러 개의 프로세스가 동시에 실행되므로 대규모 작업에 대한 처리 성능이 향상될 수 있습니다.
멀티스레딩은 하나의 프로세스 내에서 여러 개의 스레드를 동시에 실행하는 것을 의미합니다.
스레드는 프로세스 내에서 실행되는 독립적인 흐름의 단위로 프로세스의 자원을 공유하면서 실행됩니다.
멀티 스레딩은 작업을 여러 개의 작은 단위로 분할하여 동시에 처리함으로써 성능을 향상시킵니다.
스레드는 프로세스 내의 자원을 공유하므로 데이터를 공유하면서 효율적인 작업 처리가 가능하고 스레드 간의 전환 속도가 프로세스 간의 전환 속도보다 빠르기 때문에 스레드를 이용한 병렬 처리는 비교적 빠른 응답 시간을 제공할 수 있습니다.
파이썬에서의 멀티프로세싱과 멀티스레딩
파이썬에서는 multiprocessing 및 threading 모듈을 사용하여 멀티 프로세싱과 멀티 스레딩을 구현할 수 있습니다.
하지만 파이썬에서 멀티 스레딩은 CPU 바운드 작업에 대해 기대하는 성능 향상을 얻기 어렵습니다. 파이썬은 GIL(Global Interpreter Lock)이라는 인터프리터 제약 조건이 있기 때문인데요.
파이썬 인터프리터의 메모리 관리를 단순화하고 스레드 간의 동시 접근으로 인한 자원 충돌을 방지하기 위해 여러 스레드가 동시에 병렬적으로 실행되는 것이 아니라 여러 스레드를 번갈아가며 실행하는 형태를 띄기 때문입니다.
반면 멀티프로세싱은 각 프로세스가 별도의 Python interpreter를 가지므로 이 문제를 피할 수 있습니다. 따라서 멀티스레딩은 I/O 바운드 작업일 때 주로 사용되고 멀티프로세싱은 CPU 바운드 작업시에 주로 사용됩니다.
파이썬 멀티프로세싱 예제
간단하게 CPU 바운드 작업을 그냥 실행했을 때와 멀티 프로세싱을 실행했을 때로 구분하여 시간을 비교해보도록 하겠습니다.
is_prime은 소수를 판별하는 함수이고 find_primes는 start~end 구간 내의 소수를 찾는 함수입니다.
# 소수 판별 함수
def is_prime(n):
if n == 1:
return False
for i in range(2, int(n**0.5)+1):
if n % i == 0:
return False
return True
# 소수 찾는 함수
def find_primes(start, end):
primes = []
for n in range(start, end+1):
if is_prime(n):
primes.append(n)
return primes
def find_primes_wrapper(args):
return find_primes(*args)
1부터 1000000까지의 소수를 한 번에 찾을 때와 4개의 구간으로 쪼개어 멀티 프로세싱으로 각각의 구간의 소수를 찾도록 구성하였습니다.
from multiprocessing import Pool
import time
from function import *
if __name__ == "__main__":
start_time = time.time()
find_primes(1, 1000000) # 1에서 1000000까지의 소수 찾기
print(f'싱글 프로세싱 작업 소요 시간 : {time.time() - start_time}')
start_time = time.time()
with Pool(processes=4) as p:
results = p.map(find_primes_wrapper, [(1, 250000), (250001, 500000), (500001, 750000), (750001, 1000000)])
print(f'멀티 프로세싱 작업 소요 시간 : {time.time() - start_time}')
결과를 보면 단일 프로세스로 함수를 실행했을 때는 2.57초가 소요되었고, 멀티 프로세싱을 사용했을 때는 0.94초가 소요되었습니다.
멀티프로세싱을 사용했을때 2배가 훌쩍 넘게 빠른 성능을 보였습니다.
파이썬 멀티스레딩 예제
같은 CPU 바운드 함수를 멀티 스레딩으로 실행하는 코드입니다. ThreadPoolExecutor를 사용하여 최대 4개의 작업 스레드를 생성하고 각 스레드에서 find_primes 함수를 병렬로 실행하도록 설정하였습니다.
import time
from concurrent.futures import ThreadPoolExecutor
from function import *
if __name__ == "__main__":
start_time = time.time()
find_primes(1, 1000000) # 1에서 1000000까지의 소수 찾기
print(f'싱글 스레딩 작업 소요 시간 : {time.time() - start_time}')
start_time = time.time()
with ThreadPoolExecutor(max_workers=4) as executor:
results = list(executor.map(find_primes, [1, 250001, 500001, 750001], [250000, 500000, 750000, 1000000]))
print(f'멀티 스레딩 작업 소요 시간 : {time.time() - start_time}')
결과를 보면 멀티 프로세싱과 다르게 멀티 스레딩에서는 크게 시간이 단축되거나 하는 성능 개선이 이뤄지지 않았습니다.
이렇게 파이썬의 멀티 프로세싱과 멀티 스레딩에 대해서 알아보았는데요.
다음에 포스팅에서는 멀티 스레딩으로 I/O 바운드 작업을 구현해보고 성능 비교를 해보도록 하겠습니다.
Leave a comment