파이썬 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에서 잘 이용하고 있습니다.