Python 비동기 이벤트루프 방식과 멀티스레딩 방식 비교

프로필 사진mingke

python async comparison

목차

파이썬 비동기

Python에서 비동기 프로그래밍은 효율적인 I/O 작업과 고성능 애플리케이션 구축에 많이 사용됩니다. 2022년 말부터 FastAPI를 많이 사용하게 되었습니다. 그러다보니 자연스럽게 비동기 코드를 많이 작성하게 되었는데요. 그에 따라서 면접에서도 관련 질문을 많이 받게되는 것 같습니다. 최근 면접에서 ‘Python 비동기에서 이벤트루프 방식과 멀티스레딩 방식이 어떻게 다른지 설명해 보시오’ 라는 질문을 받았습니다. 오늘은 이와 관련해서 포스팅을 해보려고 합니다.

멀티스레딩 기반 비동기

멀티스레딩은 말 그대로 스레드를 여러개 만들어서 동시에 여러 작업을 실행할 수 있도록하는 기술입니다. 각 스레드는 별도의 실행경로를 가기 때문에 동시성을 실현할 수 있습니다.

장점

  • CPU 코어 수가 많으면 그것을 활용하여 많은 수의 작업을 동시에 처리할 수있습니다.
  • 구현이 쉽습니다. Python에서 threading 라이브러리를 제공하고 고수준의 concurrent.futuresThreadPoolExecutor 를 이용해서 쉽게 구현할 수 있습니다.
import threading
from concurrent.futures import ThreadPoolExecutor

단점

  • GIL(Global Interpreter Lock)으로 인하여 CPU 바운드 작업에서는 효과가 미비하거나 오히려 저하될 수 있습니다.
  • GIL은 한번에 하나의 스레드가 Python 바이트 코드를 처리하도록 합니다. 따라서 CPU 바운드 작업에서스레드간 자원공유에 대한 문제가 발생할 수 있습니다.
  • 스레드가 너무 많으면 스레드 간 컨텍스트 스위칭 오버헤드가 발생할 수 있습니다.
  • 스레드를 생성하는 것도 메모리를 사용하기 때문에 메모리 제한적인 환경에서 제대로 동작하지 않을 수 있습니다.

이벤트 루프 기반 비동기

이벤트 루프 기반 비동기 작업은 싱글 스레드에서 동작합니다. Python의 async await 예약어를 이용하여 코루틴을 만들고 그것을 이벤트 루프가 스케쥴링하고 제어하고 실행합니다. asyncio를 사용하여 비동기 코드를 효율적으로 작성할 수 있게 해줍니다.

장점

  • 효율적인 I/O 바운드 작업을 할 수 있습니다. I/O 작업이 블록되지 않고 이벤트 루프가 다른 작업으로 스위칭하여 처리하기 때문에 리소스 활용을 극대화 할 수 있습니다.
  • 컨텍스트 스위칭 오버헤드가 적어 멀티스레딩에서 발생할 수 있는 스레드 안전성 이슈가 이벤트 루프 기반 비동기 작업에서는 없습니다.

단점

  • async await 만 쓸 줄 알면 코드를 작성할 수 있지만, 이벤트 루프와 비동기 프로그래밍의 개념은 익숙해지는데 시간이 좀 걸립니다.
    • Coroutine, Future 와 같은 요소들은 동기 코드만 작성할 땐 몰랐던 요소들이죠.
    • asynchronous generator , iterator 와 같은 요소들은 실제 코드에 적용하는데 꽤 애를 먹었던 경험이 있습니다.
  • 싱글 스레드에서 실행되기 때문에 높은 CPU 사용율를 요구하는 CPU 바운드 작업에서 여러 CPU 코어를 활용하지 못하게 됩니다.
    • 이러한 이유로 FastAPI를 배포할 때 uvicorn 워커를 gunicorn에 붙여서 사용하는게 아닌가 생각됩니다.
    • gunicorn의 worker는 각각 독립적인 프로세스이기 때문에 CPU 코어가 많으면 gunicorn worker를 많이 스폰할 수 있습니다.

마무리

Python에서 이벤트 루프 방식과 멀티스레딩 방식의 비동기에 대해서 비교해봤습니다. 두 가지를 비교했지만 이벤트 루프 기반의 비동기 프로그래밍에서도 멀티스레딩을 함께 사용할 수 있습니다. 오해 없으시길 …

현대 애플리케이션에서 높은 성능을 내기 위해서는 비동기 프로그래밍은 필수적인 것 같습니다. 무엇이 정답이라기보단 상황에 맞게 적절한 기술을 사용하면 될 것 같습니다.