[모던 Python 5편] concurrent.futures, asyncio로 동시성/병렬 코드 간소화하기

과거 파이썬에서 동시성이나 병렬 처리를 구현하려면 threading이나 multiprocessing 모듈을 직접 다루며 복잡한 스레드 생성, 락(Lock) 관리, 프로세스간 통신(IPC) 코드를 작성해야 했습니다. 이는 코드가 장황해지고 디버깅이 어려워지는 문제를 일으켰습니다.

etc-image-0

Python 3.2+에서 도입된 concurrent.futures 모듈과 3.4+의 asyncio는 이러한 문제를 완화하고, 더 직관적이고 Pythonic한 방식으로 동시성 작업을 처리할 수 있게 합니다.

이번 글에서는 기존 접근법과 새로운 접근법을 비교하고, 각 방법의 장단점을 정리합니다.

이전에는 어떻게 했을까?

threading, multiprocessing

import threading

def worker():
    # 작업 처리
    pass

t = threading.Thread(target=worker)
t.start()
t.join()

단점:

  • 스레드 관리, 락 획득/해제 등 동기화 로직 작성 필요
  • 코드 복잡성 증가, 디버깅 어려움

multiprocessing도 프로세스 기반 병렬처리를 지원하지만, 큐나 파이프 등 IPC를 명시적으로 다루어야 하고, 코드가 복잡해질 수 있음.

concurrent.futures: 고급 추상화 제공

concurrent.futures 모듈의 ThreadPoolExecutorProcessPoolExecutor를 사용하면 스레드/프로세스풀 관리가 쉬워집니다.

from concurrent.futures import ThreadPoolExecutor

def worker():
    # 일부 계산 작업
    return 42

with ThreadPoolExecutor(max_workers=4) as executor:
    future = executor.submit(worker)
    result = future.result() # 완료 시 결과 반환

장점:

  • 스레드나 프로세스풀을 명시적으로 관리 필요 없음
  • future 객체로 비동기 결과 핸들링 용이
  • 코드 가독성↑, 간단한 API로 병렬 처리 구현

단점:

  • 여전히 GIL(Global Interpreter Lock)의 영향으로 CPU 바운드 작업 시 ThreadPoolExecutor 제한. CPU 바운드 작업은 ProcessPoolExecutor나 다른 전략 필요
  • 매우 복잡한 동시성 로직은 추가 관리 필요

asyncio: 비동기 I/O 기반 동시성

asyncio는 async/await 문법을 통해 비동기 I/O를 손쉽게 처리할 수 있습니다. 특히 네트워크 요청, 파일 I/O 등 I/O 바운드 작업에 뛰어난 성능 향상을 기대할 수 있습니다.

import asyncio

async def fetch_data():
    await asyncio.sleep(1) # 비동기 I/O 모사
    return "data"

async def main():
    result = await fetch_data()
    print(result)

asyncio.run(main())

장점:

  • async/await 문법으로 비동기 코드를 동기 코드처럼 작성 가능 (제어 흐름 명확)
  • I/O 바운드 작업 성능 개선
  • 에코 서버, 웹 크롤러, 비동기 DB 접근 등에 활용성↑

단점:

  • async/await 패러다임 이해 필요
  • 기존 동기 코드와 섞어 사용할 때 적응 필요

성능 측면

  • I/O 바운드 작업: asyncio를 사용하면 스레드보다 효율적으로 수천 개의 동시 연결 처리 가능
  • CPU 바운드 작업: ProcessPoolExecutor나 multiprocessing 필요. asyncio는 I/O 바운드에 초점
  • 적절한 도구 선택이 핵심: CPU 바운드면 multiprocessing, I/O 바운드면 asyncio로 분명한 전략 수립 가능

결론

  • 이전 방식: threading, multiprocessing 직접 사용 → 락 관리, IPC로 코드 복잡
  • 새로운 방식: concurrent.futures로 스레드/프로세스풀 관리 간소화, asyncio로 비동기 I/O에 최적화
  • 장점: 가독성 증가, 코드 간결, 성능 개선(I/O 바운드), 유지보수성↑
  • 단점: async/await 문법 학습 필요, CPU 바운드 작업은 별도 전략 필요

다음 글에서는 패키징, 빌드, 의존성 관리를 현대적으로 해결하는 pyproject.toml와 Poetry를 소개하여, 기존 setup.py/requirements.txt 방식 대비 훨씬 깔끔한 프로젝트 구성 방법을 알아보겠습니다.

반응형