Python 비동기 이벤트루프 방식과 멀티스레딩 방식 비교
목차
파이썬 비동기
Python에서 비동기 프로그래밍은 효율적인 I/O 작업과 고성능 애플리케이션 구축에 많이 사용됩니다. 2022년 말부터 FastAPI를 많이 사용하게 되었습니다. 그러다보니 자연스럽게 비동기 코드를 많이 작성하게 되었는데요. 그에 따라서 면접에서도 관련 질문을 많이 받게되는 것 같습니다. 최근 면접에서 ‘Python 비동기에서 이벤트루프 방식과 멀티스레딩 방식이 어떻게 다른지 설명해 보시오’ 라는 질문을 받았습니다. 오늘은 이와 관련해서 포스팅을 해보려고 합니다.
멀티스레딩 기반 비동기
멀티스레딩은 말 그대로 스레드를 여러개 만들어서 동시에 여러 작업을 실행할 수 있도록하는 기술입니다. 각 스레드는 별도의 실행경로를 가기 때문에 동시성을 실현할 수 있습니다.
장점
- CPU 코어 수가 많으면 그것을 활용하여 많은 수의 작업을 동시에 처리할 수있습니다.
- 구현이 쉽습니다. Python에서
threading
라이브러리를 제공하고 고수준의concurrent.futures
의ThreadPoolExecutor
를 이용해서 쉽게 구현할 수 있습니다.
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에서 이벤트 루프 방식과 멀티스레딩 방식의 비동기에 대해서 비교해봤습니다. 두 가지를 비교했지만 이벤트 루프 기반의 비동기 프로그래밍에서도 멀티스레딩을 함께 사용할 수 있습니다. 오해 없으시길 …
현대 애플리케이션에서 높은 성능을 내기 위해서는 비동기 프로그래밍은 필수적인 것 같습니다. 무엇이 정답이라기보단 상황에 맞게 적절한 기술을 사용하면 될 것 같습니다.