[python] concurrent.futures ProcessPoolExecutor vs ThreadPoolExecutor

Seunghyun Moon·2024년 1월 19일
0

python

목록 보기
4/4

오늘의 이슈

코드 짤 때 별 일 없으면 반복문으로 for 문을 사용합니다. 성능 중요하게 코드 짤 일이 별로 없었거든요. 병렬처리, 동시처리 어떻게 하는지 보다가, I/O bound task, CPU bound task 등 쉽게 이해가지 않는 용어에 대해서 정리해보려고 합니다.


문제 접근

문제 코드

session = init_aws_session()
s3 = session.client('s3')

for file in files:
	s3.upload_fileobj(file.file, aws_config.BUCKET_NAME, file.filename)

파일 리스트에서 파일을 하나씩 업로드 해야합니다.
동시에 처리해봅시다.

처리 방법

import concurrent.futures

def upload_file(file):
    session = init_aws_session()
    s3 = session.client('s3')
    s3.upload_fileobj(file.file, aws_config.BUCKET_NAME, file.filename)

files = [...]  # Your list of files

with concurrent.futures.ThreadPoolExecutor() as executor:
    executor.map(upload_file, files)

잘 실행됩니다.

근데 아래 코드로도 사용 가능합니다.

with concurrent.futures.ProcessPoolExecutor() as executor:
    executor.map(upload_file, files)

ProcessPoolExecutor vs ThreadPoolExecutor

비교해봅니다.

ThreadPoolExecutor:

스레드는 프로세스에 비해 가볍고 메모리 오버헤드가 낮습니다.

I/O bound task에 더 적합힙니다.

외부 리소스(예: 네트워크 또는 디스크 I/O)를 기다리는 동안 스레드가 겹칠 수 있는 I/O 바인딩 작업에 적합합니다.

스레드는 동일한 메모리 공간을 공유하므로 특정 데이터 공유 시나리오에 유리할 수 있습니다.

GIL(글로벌 통역사 잠금):

CPython(기본 Python 구현)에서 GIL(Global Interpreter Lock)은 여러 네이티브 스레드가 Python 바이트코드를 병렬로 실행하는 것을 방지합니다. 따라서 ThreadPoolExecutor의 스레드는 CPU 바인딩된 작업에 대한 진정한 병렬 처리를 제공하지 않을 수 있습니다.

ProcessPoolExecutor:

CPU bound task에 더 적합힙니다.

각 프로세스에는 자체 인터프리터와 메모리 공간이 있어 독립적으로 실행할 수 있으므로 진정한 병렬성이 필요한 CPU 바인딩 작업에 적합합니다.

GIL 프리:
프로세스는 독립적으로 작동하므로 GIL(Global Interpreter Lock)은 제한 사항이 아닙니다. 이를 통해 CPU 바인딩된 작업의 진정한 병렬 실행이 가능해졌습니다.
격리:

프로세스는 더 나은 격리를 제공하므로 작업이 서로의 메모리 공간을 방해해서는 안 되는 시나리오에 유용할 수 있습니다.

프로세스를 생성하고 관리하는 것은 스레드에 비해 더 많은 오버헤드를 갖습니다. 프로세스 간 통신은 추가적인 복잡성을 초래할 수도 있습니다.


I/O bound vs CPU bound

I/O bound task:

파일 읽기 또는 쓰기, 네트워크 통신, 데이터베이스 쿼리 또는 사용자 입력과 같은 입력/출력 작업을 기다리는 데 상당한 시간을 소비하는 작업입니다.

CPU bound task:

CPU가 I/O와 같은 외부 작업을 기다리지 않고 적극적으로 계산을 수행하는 주로 계산 및 처리와 관련된 작업입니다.


profile
I live fullest

0개의 댓글