🚀
고급 (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 지원 필요 |
자주 하는 실수
- async def 결과를 await 안 함 — 코루틴 객체만 반환
- 동기 함수 안에서 await — async def 안에서만
- 블로킹 함수 호출 — time.sleep 대신 await asyncio.sleep
- asyncio.run 중첩 — Jupyter 등에서는 await main() 사용
FAQ
Q1. requests 를 asyncio 안에서? — 동작은 함. 하지만 블로킹이라 동시성 효과 없음. httpx 사용.
Q2. gather vs wait? — gather 는 결과를 순서대로, 실패 시 전파. wait 는 더 유연.
Q3. async 디버깅? — traceback 이 다소 복잡. 디버거 도움.
💻 예제 (examples)
실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.
01_basic.py— async/await 기본
CODE
import asyncio
async def hello():
print("hello")
await asyncio.sleep(0.1)
print("world")
asyncio.run(hello())▶ 실행 결과
hello
world02_gather.py— gather 동시 실행
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.0s03_queue.py— Queue 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
소비: 204_timeout.py— wait_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())▶ 실행 결과
시간 초과