본문 바로가기

언어/Python

[ Python ] ThreadPoolExecutor - 쉽게 병렬 작업하기

반응형

ThreadPoolExecutor는 Python의 concurrent.futures 모듈에서 제공하는 클래스 중 하나로, 스레드 풀을 생성하여 멀티스레딩 작업을 효율적으로 관리할 수 있도록 도와줍니다. 이를 통해 여러 작업을 병렬로 실행할 수 있으며, 개발자는 스레드를 직접 생성하거나 관리할 필요 없이 간단한 API를 사용할 수 있습니다.

주요 특징

  1. 스레드 풀 관리: ThreadPoolExecutor는 미리 지정된 개수의 스레드를 풀에 생성하고 이를 재사용하므로 스레드 생성과 종료에 드는 비용을 줄여줍니다.
  2. 비동기 작업 관리: 작업을 비동기로 실행하고, 결과를 Future 객체를 통해 확인하거나 처리할 수 있습니다.
  3. 간단한 인터페이스: 직관적인 메서드(submit, map 등)를 제공하여 작업을 쉽게 병렬화할 수 있습니다.

주요 메서드

1. submit(fn, *args, **kwargs)

  • 비동기 작업을 제출합니다.
  • 반환값: Future 객체 (결과를 가져오거나 작업 상태를 확인할 수 있음)
from concurrent.futures import ThreadPoolExecutor

def task(n):
    return n * 2

with ThreadPoolExecutor(max_workers=3) as executor:
    future = executor.submit(task, 5)
    print(future.result())  # 10

2. map(func, *iterables)

  • 입력 iterable에 대해 함수를 병렬로 실행합니다.
  • 반환값: 결과를 포함한 iterator
def square(n):
    return n ** 2

with ThreadPoolExecutor(max_workers=3) as executor:
    results = executor.map(square, [1, 2, 3, 4, 5])
    print(list(results))  # [1, 4, 9, 16, 25]

3. shutdown(wait=True)

  • 모든 작업이 완료될 때까지 기다린 후 스레드 풀을 정리합니다.
  • wait=False로 설정하면 즉시 종료를 시도합니다.

주요 활용 예시

1. 멀티스레딩으로 웹 페이지 다운로드

import time
import requests
from concurrent.futures import ThreadPoolExecutor

urls = ["https://example.com"] * 5

def fetch_url(url):
    response = requests.get(url)
    return len(response.content)

with ThreadPoolExecutor(max_workers=5) as executor:
    results = executor.map(fetch_url, urls)
    print(list(results))

2. 작업 완료 확인

Future 객체를 사용해 작업의 완료 여부를 확인하거나 결과를 비동기로 처리합니다.

import time
from concurrent.futures import ThreadPoolExecutor

def task(n):
    time.sleep(n)
    return f"Task {n} done"

with ThreadPoolExecutor(max_workers=3) as executor:
    futures = [executor.submit(task, i) for i in range(1, 4)]

    for future in futures:
        print(future.result())  # 각 작업이 완료된 결과 출력

주의사항

  1. GIL(Global Interpreter Lock):
    • Python의 표준 구현인 CPython에서는 GIL 때문에 멀티스레딩이 CPU 바운드 작업에서 효율적이지 않을 수 있습니다.
    • 대신 I/O 바운드 작업(네트워크, 파일 I/O)에서 효과적입니다.
    • CPU 바운드 작업은 ProcessPoolExecutor를 사용하는 것이 좋습니다.
  2. 스레드 안전성:
    • 공유 데이터에 접근할 때는 threading.Lock 등의 동기화 도구를 사용하여 데이터 무결성을 유지해야 합니다.
  3. 풀 크기 설정:
    • max_workers는 시스템 자원과 작업의 성격에 맞게 조정해야 합니다. 너무 많은 스레드는 오히려 성능 저하를 초래할 수 있습니다.

ThreadPoolExecutor는 간단한 API와 효율적인 스레드 관리로 I/O 중심의 멀티스레딩 작업에서 매우 유용합니다. submitmap의 차이를 이해하고 작업의 특성에 맞게 사용하면 큰 도움이 됩니다. 😊

반응형