Skip to content

Python의 async/await를 활용한 비동기 프로그래밍을 기초부터 실전까지 완벽하게 설명합니다. 동시 작업 처리, 웹 요청 최적화, 그리고 흔한 실수들을 피하는 방법까지 다룹니다.

비동기 프로그래밍, 왜 필요할까요?

여러분이 카페에서 커피를 주문했다고 상상해보세요. 주문 후 커피가 나올 때까지 카운터 앞에서 멍하니 서 있나요? 아니면 자리에 앉아 스마트폰을 보거나 책을 읽나요? 후자가 훨씬 효율적이겠죠.

Python의 비동기 프로그래밍도 같은 원리입니다. 파일 다운로드, 데이터베이스 쿼리, API 요청처럼 시간이 걸리는 작업을 기다리는 동안, 다른 작업을 처리할 수 있게 해줍니다. 특히 I/O 작업이 많은 웹 크롤링, 웹 서버, 채팅 애플리케이션에서 엄청난 성능 향상을 가져다줍니다.

동기 방식에서는 하나의 작업이 완료될 때까지 다음 작업이 대기하지만, 비동기 방식에서는 여러 작업을 동시에 진행할 수 있어 전체 실행 시간을 크게 단축시킬 수 있습니다.

async와 await, 기본 문법 이해하기

비동기 프로그래밍의 핵심은 asyncawait 키워드입니다. 먼저 간단한 예제로 시작해볼까요?

python import asyncio

async def make_coffee(): print("커피 제조 시작") await asyncio.sleep(3) # 3초 대기 (실제로는 I/O 작업) print("커피 완성") return "아메리카노"

async def main(): coffee = await make_coffee() print(f"{coffee} 준비 완료!")

asyncio.run(main())

async def로 정의된 함수를 **코루틴(coroutine)**이라고 부릅니다. 이는 일반 함수와 달리 실행을 중간에 멈추고 다른 작업을 할 수 있는 특별한 함수입니다.

await 키워드는 "이 작업이 완료될 때까지 기다리되, 그동안 다른 작업을 처리해도 돼"라는 의미입니다. 일반 함수에서는 사용할 수 없고, 반드시 async def 내부에서만 사용해야 합니다.

💡 핵심 포인트: async def로 정의한 함수를 호출하면 즉시 실행되지 않고 코루틴 객체가 반환됩니다. 실제 실행하려면 await를 사용하거나 asyncio.run()으로 감싸야 합니다.

여러 작업 동시에 처리하기

비동기 프로그래밍의 진정한 힘은 여러 작업을 동시에 처리할 때 드러납니다.

python import asyncio import time

async def fetch_data(id, delay): print(f"작업 {id} 시작") await asyncio.sleep(delay) print(f"작업 {id} 완료") return f"데이터 {id}"

async def main(): start = time.time()

# 동기 방식 (순차 실행)
# result1 = await fetch_data(1, 2)
# result2 = await fetch_data(2, 2)
# result3 = await fetch_data(3, 2)
# 총 6초 소요

# 비동기 방식 (동시 실행)
results = await asyncio.gather(
    fetch_data(1, 2),
    fetch_data(2, 2),
    fetch_data(3, 2)
)

end = time.time()
print(f"결과: {results}")
print(f"총 실행 시간: {end - start:.2f}초")  # 약 2초!

asyncio.run(main())

asyncio.gather()는 여러 코루틴을 동시에 실행하고 모든 결과를 기다립니다. 위 예제에서 동기 방식은 6초가 걸리지만, 비동기 방식은 2초만 소요됩니다!

다른 유용한 함수들도 알아두세요: - asyncio.create_task(): 코루틴을 태스크로 스케줄링하여 백그라운드에서 실행 - asyncio.wait_for(): 타임아웃 설정 가능 - asyncio.as_completed(): 완료되는 순서대로 결과 처리

실전 활용: 비동기 웹 요청

실제 프로젝트에서 가장 많이 사용하는 예시를 살펴보겠습니다. aiohttp 라이브러리를 사용한 비동기 웹 요청입니다.

python import asyncio import aiohttp

async def fetch_url(session, url): async with session.get(url) as response: return await response.text()

async def main(): urls = [ 'https://api.example.com/data1', 'https://api.example.com/data2', 'https://api.example.com/data3' ]

async with aiohttp.ClientSession() as session:
    tasks = [fetch_url(session, url) for url in urls]
    results = await asyncio.gather(*tasks)

for i, result in enumerate(results, 1):
    print(f"응답 {i}: {len(result)} bytes")

asyncio.run(main())

⚠️ 주의사항: - requests 라이브러리는 비동기를 지원하지 않습니다. aiohttp, httpx 같은 비동기 라이브러리를 사용하세요. - 데이터베이스 작업도 aiomysql, asyncpg 같은 비동기 드라이버가 필요합니다. - CPU 집약적 작업(복잡한 계산)에는 비동기가 도움이 안 됩니다. 이럴 땐 multiprocessing을 사용하세요.

실수하기 쉬운 포인트와 디버깅 팁

비동기 프로그래밍을 처음 접하면 흔히 하는 실수들이 있습니다.

1. await 없이 코루틴 호출하기 python

잘못된 예

result = fetch_data(1, 2) # 코루틴 객체만 반환됨

올바른 예

result = await fetch_data(1, 2)

2. 동기 함수에서 await 사용하기 python

에러 발생!

def normal_function(): await some_coroutine() # SyntaxError

async 함수로 변경 필요

async def async_function(): await some_coroutine() # OK

3. 예외 처리 잊지 않기 python async def safe_fetch(url): try: async with session.get(url) as response: return await response.text() except asyncio.TimeoutError: print(f"{url} 타임아웃 발생") return None except Exception as e: print(f"에러 발생: {e}") return None

💡 디버깅 팁: asyncio.run(main(), debug=True)로 실행하면 자세한 디버그 정보를 볼 수 있습니다. 또한 완료되지 않은 태스크를 추적하려면 asyncio.all_tasks()를 활용하세요.

비동기 프로그래밍은 처음엔 낯설지만, 개념을 이해하고 나면 엄청난 성능 개선을 가져다줍니다. 작은 프로젝트부터 시작해서 점차 확장해보세요!


이 글은 AI가 자동으로 작성했습니다.