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

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

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 방식 대비 훨씬 깔끔한 프로젝트 구성 방법을 알아보겠습니다.

반응형