← 파이썬 강의 목록으로
🚀
고급 (Advanced)
async/await · gather · Queue · timeout

5주차 — 동시성 (2) asyncio

async/await 문법, asyncio.gather 로 동시 실행, Queue 기반 producer/consumer, 그리고 wait_for 타임아웃까지 — 비동기 프로그래밍의 핵심을 익힙니다.

asyncioasync/awaitgatherqueue
소요 시간
2시간
난이도
📊 고급
선수 조건
🎯 고급 4주차
결과물
비동기 동시 실행과 Queue 패턴

이 강의에서 배우는 것

  • 1async/await 문법을 사용한다
  • 2asyncio.run, asyncio.gather 로 동시 실행한다
  • 3비동기 HTTP 호출(httpx, aiohttp)을 한다
  • 4producer/consumer 패턴을 구현한다

1. 코루틴

async def 로 정의된 함수는 코루틴. 호출해도 즉시 실행 안 되고 객체 반환.

python
import asyncio

async def hello():
    print("hello")
    await asyncio.sleep(1)
    print("world")

asyncio.run(hello())

2. asyncio.gather

python
async def task(name, delay):
    await asyncio.sleep(delay)
    print(f"{name} 완료")

async def main():
    await asyncio.gather(
        task("A", 1),
        task("B", 2),
        task("C", 1),
    )

asyncio.run(main())
# 약 2초 소요 (순차면 4초)

3. 비동기 HTTP

python
import asyncio
import httpx

async def fetch(client, url):
    r = await client.get(url)
    return len(r.text)

async def main():
    async with httpx.AsyncClient() as client:
        tasks = [fetch(client, "https://example.com") for _ in range(10)]
        results = await asyncio.gather(*tasks)
    print(results)

asyncio.run(main())

requests 는 동기, httpx 와 aiohttp 가 비동기.

4. producer / consumer

python
async def producer(q):
    for i in range(5):
        await q.put(i)
        print(f"생산: {i}")
        await asyncio.sleep(0.1)
    await q.put(None)

async def consumer(q):
    while True:
        item = await q.get()
        if item is None:
            break
        print(f"소비: {item}")

async def main():
    q = asyncio.Queue()
    await asyncio.gather(producer(q), consumer(q))

5. 타임아웃

python
try:
    result = await asyncio.wait_for(slow_task(), timeout=1.0)
except asyncio.TimeoutError:
    print("시간 초과")

6. 동기 vs 비동기

스레딩asyncio
단위OS 스레드코루틴
컨텍스트 스위칭OS 결정await 에서만
메모리무거움가벼움
라이브러리대부분 호환async 지원 필요

자주 하는 실수

  1. async def 결과를 await 안 함 — 코루틴 객체만 반환
  2. 동기 함수 안에서 await — async def 안에서만
  3. 블로킹 함수 호출 — time.sleep 대신 await asyncio.sleep
  4. asyncio.run 중첩 — Jupyter 등에서는 await main() 사용

FAQ

Q1. requests 를 asyncio 안에서? — 동작은 함. 하지만 블로킹이라 동시성 효과 없음. httpx 사용.

Q2. gather vs wait? — gather 는 결과를 순서대로, 실패 시 전파. wait 는 더 유연.

Q3. async 디버깅? — traceback 이 다소 복잡. 디버거 도움.

💻 예제 (examples)

실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.

01_basic.pyasync/await 기본
CODE
import asyncio

async def hello():
    print("hello")
    await asyncio.sleep(0.1)
    print("world")

asyncio.run(hello())
▶ 실행 결과
hello
world
02_gather.pygather 동시 실행
CODE
import asyncio
import time

async def task(name, delay):
    await asyncio.sleep(delay)
    return f"{name}({delay}s)"

async def main():
    start = time.perf_counter()
    results = await asyncio.gather(
        task("A", 1),
        task("B", 2),
        task("C", 1),
    )
    print(results)
    print(f"총 {time.perf_counter() - start:.1f}s")

asyncio.run(main())
▶ 실행 결과
['A(1s)', 'B(2s)', 'C(1s)']
총 2.0s
03_queue.pyQueue producer/consumer
CODE
import asyncio

async def producer(q):
    for i in range(3):
        await q.put(i)
        print(f"생산: {i}")
    await q.put(None)

async def consumer(q):
    while True:
        item = await q.get()
        if item is None:
            break
        print(f"소비: {item}")

async def main():
    q = asyncio.Queue()
    await asyncio.gather(producer(q), consumer(q))

asyncio.run(main())
▶ 실행 결과
생산: 0
생산: 1
생산: 2
소비: 0
소비: 1
소비: 2
04_timeout.pywait_for 타임아웃
CODE
import asyncio

async def slow():
    await asyncio.sleep(2)
    return "완료"

async def main():
    try:
        result = await asyncio.wait_for(slow(), timeout=1.0)
        print(result)
    except asyncio.TimeoutError:
        print("시간 초과")

asyncio.run(main())
▶ 실행 결과
시간 초과

📝 과제 (exercises)

직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.

과제 1

동시 fetch 시간 측정

목표: 여러 가짜 fetch 를 동시 실행해 시간을 측정한다.

요구사항
  • fetch(i) 가 1초 sleep
  • 5개를 gather 로 동시 실행
  • 총 시간 출력
💡 힌트

time.perf_counter()

입출력 예시
[0, 1, 2, 3, 4]
총 1.0s
채점
  • · gather 사용
  • · 1초 안팎
정답 코드 펼치기 / 접기
SOLUTION
import asyncio
import time

async def fetch(i):
    await asyncio.sleep(1)
    return i

async def main():
    start = time.perf_counter()
    results = await asyncio.gather(*(fetch(i) for i in range(5)))
    print(results)
    print(f"총 {time.perf_counter() - start:.1f}s")

asyncio.run(main())
▶ 실행 결과
[0, 1, 2, 3, 4]
총 1.0s
과제 2

Queue 작업 분배

목표: 1개 producer, 2개 consumer 로 작업을 나눠 처리한다.

요구사항
  • producer 가 5개 작업 put
  • consumer 2개가 동시에 get/처리
  • 종료 신호 처리
💡 힌트

q.put(None) 을 consumer 수만큼

입출력 예시
소비1: 0
소비2: 1
소비1: 2
... (순서는 다를 수 있음)
채점
  • · Queue 사용
  • · 두 consumer 동작
정답 코드 펼치기 / 접기
SOLUTION
import asyncio

async def producer(q, n):
    for i in range(n):
        await q.put(i)
    for _ in range(2):
        await q.put(None)

async def consumer(q, name):
    while True:
        item = await q.get()
        if item is None:
            break
        print(f"{name}: {item}")
        await asyncio.sleep(0.05)

async def main():
    q = asyncio.Queue()
    await asyncio.gather(
        producer(q, 5),
        consumer(q, "소비1"),
        consumer(q, "소비2"),
    )

asyncio.run(main())
▶ 실행 결과
소비1: 0
소비2: 1
소비1: 2
소비2: 3
소비1: 4
과제 3

타임아웃 fetch

목표: 느린 fetch 에 1초 타임아웃을 걸어 처리한다.

요구사항
  • asyncio.wait_for 사용
  • TimeoutError 처리
💡 힌트

except asyncio.TimeoutError

입출력 예시
시간 초과
채점
  • · timeout 동작
  • · 예외 처리
정답 코드 펼치기 / 접기
SOLUTION
import asyncio

async def slow_fetch():
    await asyncio.sleep(3)
    return "데이터"

async def main():
    try:
        result = await asyncio.wait_for(slow_fetch(), timeout=1)
        print(result)
    except asyncio.TimeoutError:
        print("시간 초과")

asyncio.run(main())
▶ 실행 결과
시간 초과
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗