Python 과 비동기 (asyncio)
Python 과 비동기 (asyncio)
Python 은 1991 년에 처음 공개된 동적 스크립트 언어. 이 글은 Python 의 흐름 · GIL · asyncio 등장 배경. 동시성과 병렬성의 차이 · ASGI 와 FastAPI 의 위치 · trio · anyio 같은 대안.
1. Python 에 대한 이야기
네덜란드 출신 Guido van Rossum 이 1989 년 크리스마스 휴가에 시작해 1991 년 2월 0.9.0 으로 처음 공개. 1.0 은 1994 년, 2.0 은 2000 년, 3.0 은 2008 년. 2.x 와 3.x 가 호환되지 않아 한동안 두 갈래로 굴러갔고 2020 년 1월 1일자로 2.7 의 공식 지원이 끝남.
언어 자체는 PSF (Python Software Foundation) 가 관리하고, 변경은 PEP (Python Enhancement Proposal) 절차를 통합니다.
2. GIL
CPython (레퍼런스 구현) 의 인터프리터는 한 번에 한 스레드만 Python 바이트코드를 실행하도록 GIL 을 잡음. 1992 년 무렵부터 있었던 설계이며, 단일 스레드 성능과 C 확장 작성의 단순성을 위한 절충.
결과:
- CPU 바운드 작업 은 멀티스레드로 병렬화되지 않음 (같은 프로세스 안에서).
- I/O 바운드 작업 은 GIL 을 자주 놓기 때문에 멀티스레드가 의미.
- 진짜 병렬을 원하면
multiprocessing으로 프로세스 분리.
PEP 703 (2023, Sam Gross) 가 GIL 을 선택적으로 끄는 빌드 모드를 제안했고, 3.13 (2024-10) 에서 실험적 free-threaded 빌드 (--disable-gil) 가 제공되기 시작. 정식화는 단계적으로 진행 중.
3. 동시성 vs 병렬성
| 구분 | 의미 | 도구 |
|---|---|---|
| 동시성 (concurrency) | 여러 작업을 같은 시간대에 진행하는 구조. 한 코어로도 가능. | asyncio · threading |
| 병렬성 (parallelism) | 같은 순간에 여러 코어가 동시에 실행. | multiprocessing · C 확장 |
GIL 때문에 Python 의 멀티스레드는 동시성에는 쓸모가 있어도 CPU 병렬에는 한계. 이 빈자리에 multiprocessing (3.0, 2008) 과 concurrent.futures (3.2, 2011).
4. asyncio 의 등장
| 버전 | 사건 |
|---|---|
| 3.4 (2014) | asyncio 표준 라이브러리 추가. generator 기반 @asyncio.coroutine. |
| 3.5 (2015) | PEP 492. async def · await 키워드. |
| 3.6 (2016) | async generator · async comprehension. |
| 3.7 (2018) | asyncio.run() · ContextVar. |
| 3.11 (2022) | TaskGroup · except*. |
asyncio 는 이벤트 루프 위에서 코루틴을 협력적으로 스케줄. await 를 만나면 코루틴이 자발적으로 양보하고, 이벤트 루프는 다른 준비된 코루틴을 깨움:
import asyncio
import httpx
async def fetch(url: str) -> int:
async with httpx.AsyncClient() as c:
r = await c.get(url)
return r.status_code
async def main():
async with asyncio.TaskGroup() as tg: # 3.11+
t1 = tg.create_task(fetch("https://example.com"))
t2 = tg.create_task(fetch("https://www.python.org"))
print(t1.result(), t2.result())
asyncio.run(main())
TaskGroup 은 한 작업이 실패하면 형제 작업을 자동으로 취소하고 모든 예외를 ExceptionGroup 으로 모음. "구조적 동시성 (structured concurrency)" 이라 부름.
5. ASGI 의 위치
WSGI (2003, PEP 333 / 3333) 는 동기 인터페이스. 비동기 프레임워크가 등장하면서 ASGI (Asynchronous Server Gateway Interface) 가 정의. Tom Christie 가 2018 년 무렵 Django Channels · Starlette 작업 중 다듬음.
- Starlette (Tom Christie, 2018) — 가벼운 ASGI 프레임워크.
- FastAPI (Sebastián Ramírez, 2018) — Starlette 위에 Pydantic 으로 타입 · 검증 · OpenAPI 자동 생성을 더한 프레임워크.
- Uvicorn (2017) · Hypercorn — ASGI 서버 구현.
6. 다른 길
비동기 모델 자체에 다른 시도:
| 도구 | 시작 | 특징 |
|---|---|---|
| asyncio | 2014, 표준 | 가장 넓게 쓰임. |
| trio | 2017, Nathaniel J. Smith | 구조적 동시성을 처음부터 설계 원칙으로. nursery 개념. |
| anyio | 2018, Alex Grönholm | trio 와 asyncio 양쪽 위에서 같은 코드를 돌릴 수 있는 추상층. Starlette · FastAPI 가 내부에서 사용. |
| curio | 2016, David Beazley | 실험적. asyncio 의 대안 설계 탐구. |
asyncio.TaskGroup 은 trio 의 nursery 영향을 받은 구조.
동기 프레임워크와의 비교:
| 프레임워크 | 모델 | 비고 |
|---|---|---|
| Django | WSGI (동기) + ASGI (부분) | 2005. 가장 큰 생태계. |
| Flask | WSGI (동기) | 2010. 마이크로 프레임워크의 원형. |
| FastAPI | ASGI (비동기) | 2018. 타입 힌트 기반 자동 검증 · 문서화. |
| Sanic | ASGI (비동기) | 2016. 초기 비동기 시도 중 하나. |
비동기가 만능은 아닙니다. 라이브러리 · 드라이버가 비동기 인터페이스를 제공하지 않으면 이벤트 루프를 막아 오히려 느려짐. CPU 바운드 작업도 마찬가지. 이런 자리에는 asyncio.to_thread (3.9+) 또는 별도 워커 프로세스.
7. 자주 쓰는 모양
import asyncio, httpx
async def fetch_all(urls: list[str]) -> list[int]:
async with httpx.AsyncClient(timeout=10) as c:
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(c.get(u)) for u in urls]
return [t.result().status_code for t in tasks]
8. 자주 걸리는 자리
동기 코드를 async 함수 안에서 그대로 호출 — CPU 작업 또는 동기 DB 드라이버가 이벤트 루프를 막음. asyncio.to_thread(fn, *args) 또는 loop.run_in_executor 로 풀어냄.
time.sleep vs asyncio.sleep — 전자는 루프 전체를 멈춤. async 함수 안에서는 await asyncio.sleep(...).
누락된 await — f() 만 호출하면 코루틴 객체만 만들어지고 실행되지 않음. 경고가 뜨지만 놓치기 쉬움.
이벤트 루프 정책 — Windows 의 기본 루프는 한동안 ProactorEventLoop. 라이브러리에 따라 SelectorEventLoop 가 필요할 수 있음.
GIL 의존성을 잊은 멀티스레드 CPU 코드 — 코어 수만큼 스레드를 띄워도 빠르지 않음. multiprocessing 또는 NumPy 같은 GIL-releasing C 확장이 필요.
CancelledError — asyncio 의 취소는 예외로 전달. except Exception 으로 삼키면 취소가 무력해짐. except 시 CancelledError 를 다시 raise 하는 패턴이 안전.
하고픈 말
Python 의 비동기는 GIL 의 한계를 이벤트 루프와 협력적 스케줄링으로 우회하는 길. asyncio + FastAPI + Pydantic + httpx 의 조합이 모던 Python 비동기의 기본 짝. CPU 바운드는 여전히 multiprocessing 또는 C 확장의 자리. PEP 703 의 free-threaded 빌드가 정식화되면 그림이 다시 한 번 바뀔 자리.
Next
- rust-for-tauri
- (languages 끝)
Python 공식 사이트 · Python Documentation asyncio · PEPs Index · PEP 492 Coroutines with async and await · PEP 703 Making the GIL Optional in CPython · trio · anyio · ASGI 명세 · FastAPI 를 참고합니다.