파이썬 Async, 왜 아직 주류가 아닐까?
(tonybaloney.github.io)파이썬 Async, 왜 아직 주류가 아닐까?
파이썬의 asyncio
는 I/O(입출력) 작업이 많은 환경에서 대기 시간을 줄여 프로그램의 효율을 극적으로 높일 수 있는 강력한 도구입니다. 하지만 여러 장점에도 불구하고 모든 파이썬 개발자가 적극적으로 사용하지는 않는데, 여기에는 몇 가지 근본적인 이유가 있습니다.
1. 머릿속이 복잡해져요: 인지적 부하
가장 큰 장벽은 복잡성입니다. 동기식 코드는 우리가 책을 읽듯 위에서 아래로 순서대로 실행 흐름을 따라가면 되기 때문에 직관적입니다.
하지만 비동기 코드는 다릅니다. 마치 한 명의 요리사가 파스타를 만들기 위해 면을 삶는 동안(대기 시간), 다른 한쪽에서는 소스를 만들고, 남는 시간에는 채소를 다듬는 것과 같습니다. 결과적으로는 더 빨리 요리를 완성할 수 있지만, 요리사의 머릿속에서는 '지금 면이 얼마나 익었지?', '소스가 타지 않나?' 등 여러 작업의 상태를 계속해서 신경 써야 합니다.
이처럼 코드의 실행 흐름이 여기저기 옮겨 다니기 때문에, 지금 어떤 작업이 실행 중이고 어떤 작업이 대기 중인지 파악하기가 어렵습니다. 이는 특히 버그가 발생했을 때 원인을 추적하는 디버깅 과정을 매우 까다롭게 만듭니다.
2. 따로 노는 생태계: 라이브러리 호환성
파이썬의 또 다른 문제는 라이브러리 생태계가 '동기'와 '비동기'로 나뉘어 있다는 점입니다. 수많은 유명하고 편리한 라이브러리들(예: 표준 HTTP 요청 라이브러리인 requests
나 많은 ORM)이 동기 방식으로만 작동합니다.
비동기 코드 안에서 동기 라이브러리를 잘못 사용하면, 그 동기 코드가 실행되는 동안 비동기의 가장 큰 장점인 '이벤트 루프'가 그대로 멈춰버립니다. 이는 여러 차선을 만들어 놓고 1차선만 이용하는 것과 같아서 비동기를 쓰는 의미가 없어집니다. 이 문제를 해결하려면 비동기를 지원하는 별도의 라이브러리(aiohttp
, asyncpg
등)를 새로 배우고 적용해야 하며, 이는 학습 곡선을 더 가파르게 만듭니다.
3. 한 번에 한 놈만 팬다: GIL (전역 인터프리터 잠금)
GIL (Global Interpreter Lock) 은 파이썬의 고질적인 특징 중 하나로, 하나의 프로세스 안에서는 여러 스레드가 있더라도 동시에 단 하나의 스레드만 실행되도록 제한하는 장치입니다. asyncio
는 단일 스레드에서 작동하므로 GIL과 직접 충돌하지는 않지만, GIL의 존재는 async
의 활용 범위를 제한합니다.
asyncio
는 I/O 대기 시간(네트워크 응답 대기, 파일 읽기 대기 등)을 활용하는 데 최적화되어 있습니다. 하지만 만약 계산이 매우 복잡한 CPU 집약적인 작업이 async
함수 내에 포함된다면, 그 계산이 끝날 때까지 이벤트 루프 전체가 멈추게 됩니다. 이 시간 동안 다른 I/O 작업들은 아무것도 하지 못하고 기다려야만 합니다. 결국, 진정한 병렬 처리가 필요한 작업에는 여전히 multiprocessing
과 같은 다른 기술을 사용해야 하는 한계가 있습니다.
4. 미래에 대한 희망: 파이썬 3.14와 GIL의 제거
하지만 이러한 한계점에 대한 매우 희망적인 소식이 있습니다. 바로 파이썬 3.13부터 실험적으로 도입되고 3.14 버전에서 더 성숙해질 것으로 기대되는 GIL의 선택적 제거 움직임입니다.
PEP 703이라는 제안을 통해 추진되는 이 변화는, 개발자가 원할 경우 GIL 없이 파이썬 코드를 실행할 수 있게 하는 것을 목표로 합니다. 이것이 현실화된다면, 파이썬에서도 여러 스레드가 여러 CPU 코어를 동시에 활용하는 진정한 멀티스레딩이 가능해집니다.
이는 asyncio
와 함께 사용될 때 엄청난 시너지를 낼 수 있습니다. I/O 작업은 asyncio
로 효율적으로 처리하고, 계산이 많이 필요한 CPU 작업은 별도의 스레드로 넘겨 GIL의 제약 없이 병렬로 처리하는 것이 훨씬 쉬워집니다. 이 변화는 파이썬 생태계에 큰 전환점이 될 것이며, async
프로그래밍의 채택을 가로막던 많은 장벽을 허물어줄 것이라는 큰 기대를 받고 있습니다.
Asyncio의 문제는 어려운 비동기 프로그래밍의 난이도가 아니라 저열한 품질입니다. 일관성과 보편성을 내다버린 설계는 파이썬에서 드물지도 않지만, ProactorEventLoop 같은 건 5년 전에 보고된 서비스 중단을 유발하는 버그가 아직도 남아있죠.
억지로 써야하는 입장에선 이런 글이 웃어넘기기 참 어렵네요.
물론 GIL 때문에 애초에 얻을 수 있는 이익이 다른 환경에 비해 적다는 것이 더 큰 이유일수는 있습니다.
GIL이 없으면 시너지를 낼 수 있다는 표현은 기만에 가깝다고 생각합니다. 한쪽 다리가 없는 달리기 선수에게 불편하나마 의족을 달아주면 그게 '시너지'인가요?
asyncio 많이 쓰는데.. 쓸만합니다.. 작업 취소가 edge-triggered (level-triggered 가 아닌) 로 된 한계가 있는데 사실 작업취소를 aware하면서 gracefully한 처리를 하는 코딩을 작성하는 일이 별로 없고, 그보다 더 큰 문제는 eventloop가 task에 대한 weak reference를 가지고 있기 때문에 gc에 의해서 사라질수 있다는 건데.. 그건 structured concurrency로 해결됩니다.
웬만한 주요 i/o 작업에 관련해서 asyncio 지원하는 라이브러리 찾는데 문제없고..
GIL? 하고는 크게 상관이 없어요.. asyncio를 CPU intensive 한 작업에 병렬로 하려고 사용한다는 접근 자체가 좀 이상하네요.. GIL이 개선되면 CPU intensive한 멀티쓰래딩에 유용하게되겠죠.. async는 i/o 병목 구간을 최대한 효율적으로 돌리는건데...
암튼 결론.. 어느 정도 디자인상의 문제는 있으나, 목적달성하는데 사용하는데 별다른 문제 없이 production에서 잘 이용하고 있습니다.
물론 저도 프로덕션에는.asyncio를 질리도록 사용하고 있습니다만, 저는 지금의 사용경험이 '잘 이용하고 있다.' 라는 평가를 내릴만큼 만족스럽진 않네요..
지금의 asyncio가 GIL을 전제로 설계되어있는, 어찌보면 GIL의 회피전략이니 GIL이asyncio와 상호작용하지는 않죠.
근데 asyncio에 기반하여 돌아가는 concurrency 프로그래밍 전체의 관점에서 보면 GIL이 무관하다는 표현은 '파이썬이니까 안되는게 당연해.' 같은 이야기가 된다고 생각합니다.
GIL은 좀 뜬금없게 나오는거 같은데.. GIL이 제거되더라도
I/O bound, CPU bound에서 모두 멀티스레드를 사용하고 싶다면
파이썬 말고 다른 대안을 채택하는게 좋지 않을지..
asyncio는 파이썬 깊게 하시는 분들의 불호가 좀 심한느낌이긴 하더라고요.
gevent가 주류가 되었어야 했다는 의견을 종종 들었던거같아요
현재의 GIL에 대한 방향성이 '다른 대안'과 비교해도 손색이 없게 만들어주리라고 기대할 수 없는 것은 동의하지만
파이썬 말고 다른 대안을 채택해야한다는 말은 문제가 없다는 논조가 아니라 문제가 있다는 논조로 이어져야 하지 않은가 싶습니다.