-
[Python] 왜 Asyncio에는 Executor가 필요한가 — Async와 Cpu-bound의 경계IT 2026. 1. 23. 11:35
async와 CPU-bound의 경계
async / await를 어느 정도 이해하고 나면, 결국 이런 질문에 도달하게 됩니다.
async로 작성했는데, 왜 어떤 코드는 여전히 느릴까?
그리고 조금 더 파고들면, run_in_executor 혹은 ThreadPoolExecutor, ProcessPoolExecutor 같은 존재를 만나게 됩니다. 이 글에서는 executor를 문법이나 사용법이 아니라, 왜 asyncio 세계에 이런 장치가 필요했는지라는 관점에서 정리해보려 합니다.
async는 모든 문제를 해결하지 않는다
async는 강력하지만, 만능은 아닙니다. async가 잘 해결하는 문제는 명확합니다.
- 네트워크 I/O
- 파일 I/O
- 대기 시간이 많은 작업
이들의 공통점은 CPU를 거의 사용하지 않는다는 점입니다. async는 이 대기 시간을 효율적으로 숨기는 구조이지, CPU 연산을 빠르게 만드는 기술이 아닙니다.
CPU-bound 작업의 등장
다음과 같은 코드를 생각해봅시다.
async def calc(): total = 0 for i in range(10**8): total += i return total문법적으로는 async 함수지만, 이 코드는 전혀 비동기적이지 않습니다. await도 없고, 실행 중에 제어권을 넘길 수도 없습니다.
이 함수가 실행되는 동안 이벤트 루프는 완전히 멈춥니다.
이벤트 루프의 한계
이벤트 루프는 다음과 같은 전제를 가지고 설계되었습니다.
- 작업은 짧게 실행된다
- 오래 걸리는 작업은 스스로 양보한다
CPU-bound 작업은 이 전제를 정면으로 깨뜨립니다. 그래서 asyncio는 이런 작업을 이벤트 루프 안에서 처리하지 않도록, 의도적으로 도망갈 출구를 만들어두었습니다. 그것이 바로 executor입니다.
executor란 무엇인가
executor는 한 문장으로 이렇게 정리할 수 있습니다.
이벤트 루프 밖에서 실행되는 작업 공간
즉,
- 이벤트 루프는 계속 돌고
- 무거운 작업은 다른 스레드나 프로세스에서 실행되며
- 결과만 다시 이벤트 루프로 돌아옵니다
이 구조 덕분에 async 프로그램은 멈추지 않습니다.
run_in_executor의 의미
loop.run_in_executor(None, blocking_func)이 코드는 단순히 “스레드에서 실행해라”라는 명령이 아닙니다. 의미를 풀면 이렇습니다.
- 이 함수는 오래 걸릴 것이다
- 이벤트 루프를 막지 않게 해달라
- 끝나면 결과만 알려달라
이것은 성능 최적화라기보다 이벤트 루프를 보호하기 위한 장치입니다.
Thread vs Process
executor에는 두 가지 선택지가 있습니다.
ThreadPoolExecutor
- I/O 성격의 블로킹 코드에 적합
- GIL의 영향을 받음
- 컨텍스트 스위칭 비용이 낮음
ProcessPoolExecutor
- CPU-bound 작업에 적합
- GIL을 우회 가능
- 프로세스 간 통신 비용 존재
여기서 중요한 점은, async 자체가 이 둘을 대체하는 기술이 아니라는 사실입니다. async는 이들을 조율하는 역할을 합니다.
async의 진짜 역할
이쯤 오면 async의 정체가 꽤 분명해집니다.
- async는 빠르게 계산하기 위한 도구가 아니라
- 기다림을 효율적으로 관리하기 위한 도구이며
- CPU를 쓰는 일은 여전히 다른 메커니즘에 맡깁니다
그래서 현대 파이썬 프로그램은 이렇게 구성됩니다.
- I/O → async
- CPU → executor / native code
- 전체 흐름 → 이벤트 루프
마무리
executor를 이해하면, async에 대한 기대치도 함께 정리됩니다.
async는 모든 것을 해결하지 않는다. 다만, 무엇을 어디에 맡겨야 하는지는 아주 명확하게 알려준다.
이 경계를 이해하는 순간, async 코드는 더 이상 느리거나 답답한 기술이 아니라, 아주 정직한 설계로 보이기 시작합니다.
'IT' 카테고리의 다른 글
[Python] Asyncio Vs Threading Vs Multiprocessing — 무엇을 선택해야 할까 (1) 2026.01.23 [Python] Async 코드가 느려지는 순간들 — 실무에서 가장 많이 하는 실수들 (0) 2026.01.23 [Python] 이벤트 루프는 어떻게 동작하는가 — Pseudo-code로 보는 Asyncio의 내부 (1) 2026.01.23 [Python] Async 핵심 개념 3부작 — 이벤트 루프, 동시성, 블로킹 (1) 2026.01.23 [Python] GIL — 파이썬은 왜 멀티코어를 막아놓았을까 (0) 2026.01.23