파이썬 교재 · 24편 / 27편

파이썬 async·await — asyncio

기다리는 동안 다른 일을. 100개 URL 호출이 1초 안에 끝나는 비밀.

실전읽는 시간 7분2026-05-13
httpx 와 asyncio.gather 로 여러 URL 을 동시에 호출하는 코드 화면

20편의 requests 로 URL 100개를 호출하면 한 번에 평균 1초씩, 총 100초. 그런데 사실 컴퓨터가 일한 시간은 0.1초도 안 됩니다 — 99% 가 서버 응답을 기다리는 시간이거든요. 한 번에 한 줄씩 순서대로 기다리는 게 비효율인 거죠. asyncio 가 이걸 해결합니다 — 한 요청이 기다리는 동안 다음 요청을 보내고, 응답이 오는 대로 처리. 100초 작업이 1-2초로 줍니다.

24편을 마치면 ① async def·await 문법 ② asyncio.gather 로 동시 실행 ③ httpx 같은 async 라이브러리 ④ 동시성 vs 병렬성 ⑤ 언제 async 가 효과 있는지 — 5가지가 손에 익습니다.

async 의 첫 모습

(.venv) $ pip install httpx

import asyncio
import httpx

async def fetch(url: str) -> int:
    async with httpx.AsyncClient(timeout=10) as client:
        r = await client.get(url)
        return r.status_code

async def main():
    urls = ["https://example.com" for _ in range(5)]
    # 순차 — 5초
    # for u in urls: await fetch(u)
    # 동시 — 1초
    results = await asyncio.gather(*(fetch(u) for u in urls))
    print(results)   # [200, 200, 200, 200, 200]

asyncio.run(main())

4가지 키워드.

  • async def — 이 함수는 코루틴(coroutine). 호출해도 즉시 안 실행, 코루틴 객체만 반환
  • await — "이 코루틴 결과를 기다린다". 기다리는 동안 다른 코루틴이 실행될 수 있게 양보
  • asyncio.gather — 여러 코루틴을 동시에 시작, 모두 끝나면 결과 리스트
  • asyncio.run — 일반 코드에서 async 코드를 시작하는 진입점

동시성 vs 병렬성 — 다른 개념

📌 같은 일을 빠르게 하는 두 방법

· 동시성(concurrency, asyncio) — "한 사람이 여러 일 동시에 신경 쓰기". CPU 1개로 충분, IO 가 많은 작업(웹 호출·DB·파일)에 적합. asyncio 의 영역

· 병렬성(parallelism, multiprocessing) — "여러 사람이 진짜 동시에 일하기". CPU 여러 개 필요, 계산 무거운 작업(이미지 처리·머신러닝)에 적합. multiprocessing 의 영역

asyncio 가 100배 빨라지는 이유는 "기다리는 시간이 길어서". 1초 걸리는 HTTP 호출 중 99% 가 그냥 기다림이라 그 사이에 다른 호출을 끼워넣는 것. CPU 가 진짜 일하는 작업(예: 큰 행렬 곱)은 asyncio 로 빨라지지 않습니다 — 그건 multiprocessing 또는 NumPy.

async 라이브러리 — 쓰던 라이브러리의 형제

# requests → httpx (또는 aiohttp)
import httpx
async with httpx.AsyncClient() as c:
    r = await c.get(url)

# sqlite3 → aiosqlite
import aiosqlite
async with aiosqlite.connect("db.sqlite") as db:
    async with db.execute("SELECT * FROM users") as cur:
        rows = await cur.fetchall()

# open → aiofiles
import aiofiles
async with aiofiles.open("log.txt") as f:
    text = await f.read()

# time.sleep → asyncio.sleep
await asyncio.sleep(2)

거의 모든 동기 라이브러리에 async 짝이 존재합니다. requests 의 async 짝은 httpx. async 코드 안에서 동기 함수(time.sleep·requests.get)를 부르면 그 동안 전체가 멈춰버려요 — 같은 그룹의 async 버전을 써야 합니다.

실전 패턴 — 100개 동시 호출 안전판

import asyncio, httpx

async def fetch(client: httpx.AsyncClient, url: str, sem: asyncio.Semaphore) -> dict:
    async with sem:                  # 동시 실행 제한 (rate-limit)
        try:
            r = await client.get(url, timeout=10)
            r.raise_for_status()
            return {"url": url, "status": r.status_code, "size": len(r.content)}
        except Exception as e:
            return {"url": url, "error": str(e)}

async def main(urls: list[str]):
    sem = asyncio.Semaphore(20)      # 최대 20개 동시
    async with httpx.AsyncClient() as client:
        tasks = [fetch(client, u, sem) for u in urls]
        results = await asyncio.gather(*tasks, return_exceptions=False)
    return results

asyncio.run(main(["https://example.com"] * 100))

Semaphore동시 실행 개수를 제한 — 1000개 URL 을 한꺼번에 던지면 본인 네트워크도 막히고 상대 서버도 차단당해요. 20-50개가 안전한 기본값.

⚠️ async 는 만능이 아닙니다. 짧은 스크립트·단일 요청·CPU 계산 위주 작업은 requests·동기 코드가 훨씬 단순. 같은 코드를 async 로 바꾸는 건 비용이 있어요(전부 await 추가·async 라이브러리 사용·디버깅 어려움). 여러 IO 를 동시에 처리할 때만 가치가 있습니다.

다음 미션: ① 20편의 GitHub 사용자 5명 정보를 순차/동시 호출로 비교 (시간 측정) ② asyncio.sleep 으로 가짜 작업 만들어 동작 확인 ③ Semaphore 로 동시 5개 제한.

다음 편 미리보기

25편 — "pytest 사용법": 자동 테스트 첫 발걸음. assert·fixture·매개변수 테스트로 자신감 있게 리팩토링.

📚 27편 파이썬 교재 시리즈 — 24/27편
← 23편 "웹 크롤링" · 다음: 25편 "pytest"

© 2026 주나이테크(주) @JUNAITECH