본문 바로가기
개발 언어/Python

파이썬 고급 5강: async/await으로 시작하는 비동기 파이썬: asyncio 기초

by 주호파파 2025. 5. 28.
728x90
반응형

 

🐍

파이썬 고급편

Advanced Python Series

5강 / 10강

async/await으로 시작하는 비동기 파이썬: asyncio 기초

 

개요 및 중요성

현대 웹 애플리케이션과 데이터 처리 시스템에서 **비동기 프로그래밍**은 필수적인 기술이 되었습니다. 파이썬의 asyncio 라이브러리는 동시에 여러 작업을 효율적으로 처리할 수 있게 해주는 강력한 도구입니다.

특히 네트워크 요청, 파일 I/O, 데이터베이스 접근과 같은 **I/O 바운드 작업**에서 극적인 성능 향상을 경험할 수 있습니다. 이번 강의에서는 async/await 키워드부터 시작해서 비동기 프로그래밍의 핵심 개념들을 실습을 통해 학습해보겠습니다.

 

asyncio 라이브러리 기초

asyncio는 파이썬 3.4+에서 제공하는 **비동기 I/O 라이브러리**입니다. 이벤트 루프를 기반으로 하여 단일 스레드에서 여러 작업을 동시에 실행할 수 있게 해줍니다.

🎯 핵심 용어 정리

이벤트 루프 (Event Loop)

작업들을 스케줄링하고 실행하는 핵심 엔진

코루틴 (Coroutine)

일시정지하고 재개할 수 있는 함수

태스크 (Task)

이벤트 루프에서 실행되는 코루틴

어웨이터블 (Awaitable)

await 키워드로 기다릴 수 있는 객체

기본 문법 소개

# 코루틴 함수 정의
async def my_coroutine():
    print("코루틴 시작")
    await asyncio.sleep(1)  # 1초 대기 (논블로킹)
    print("코루틴 완료")

# 코루틴 실행
async def main():
    await my_coroutine()

# 이벤트 루프 실행
asyncio.run(main())

async def로 코루틴 함수를 정의하고, await로 다른 코루틴의 완료를 기다립니다.

 

async/await 키워드 사용법

asyncawait는 파이썬 비동기 프로그래밍의 핵심 키워드입니다. 이들을 통해 **협력적 멀티태스킹**을 구현할 수 있습니다.

async 함수 정의

함수를 코루틴으로 만드는 키워드

async def fetch_data():
    # 비동기 작업 수행
    return "데이터"

await 실행 대기

코루틴의 완료를 기다리는 키워드

result = await fetch_data()
print(result)  # "데이터"

중요한 규칙

  • awaitasync 함수 내에서만 사용 가능
  • • 코루틴 함수를 호출하면 코루틴 객체가 반환됨 (즉시 실행되지 않음)
  • await 없이 코루틴을 호출하면 경고 발생
 

이벤트 루프와 태스크 관리

**이벤트 루프**는 asyncio의 핵심으로, 코루틴들을 스케줄링하고 실행하는 역할을 합니다. 여러 작업을 동시에 처리하려면 태스크(Task)로 변환하여 관리해야 합니다.

동시 실행 vs 순차 실행 비교

❌ 순차 실행 (비효율적)

async def sequential():
    start = time.time()
    
    await asyncio.sleep(2)  # 2초 대기
    await asyncio.sleep(2)  # 추가 2초 대기
    await asyncio.sleep(2)  # 추가 2초 대기
    
    print(f"총 소요시간: {time.time()-start:.1f}초")
    # 결과: 약 6초

✅ 동시 실행 (효율적)

async def concurrent():
    start = time.time()
    
    # 동시에 실행
    await asyncio.gather(
        asyncio.sleep(2),
        asyncio.sleep(2),
        asyncio.sleep(2)
    )
    
    print(f"총 소요시간: {time.time()-start:.1f}초")
    # 결과: 약 2초

I/O 바운드 작업에서의 효율성

비동기 프로그래밍은 특히 **I/O 바운드 작업**에서 진가를 발휘합니다. 네트워크 요청, 파일 읽기, 데이터베이스 쿼리 등에서 대기 시간 동안 다른 작업을 처리할 수 있어 전체적인 성능이 크게 향상됩니다.

예시: 100개의 웹 API를 호출하는 경우

  • • 동기 방식: 100 × 0.5초 = 50초
  • • 비동기 방식: 최대 0.5초 (네트워크 응답 시간)
 

비동기 프로그래밍 실습: 간단한 샘플 코드

이번 강의에서 학습한 내용을 바탕으로 직접 실행해 볼 수 있는 실용적인 코드 예제를 살펴보겠습니다. **다중 비동기 작업 동시 실행**과 **간단한 비동기 웹 요청** 예제를 통해 asyncio의 활용법을 익혀보세요.

예제 1 다중 비동기 작업 동시 실행

import asyncio
import time

async def cook_dish(dish_name, cook_time):
    """요리를 만드는 비동기 함수"""
    print(f"🍳 {dish_name} 요리 시작!")
    await asyncio.sleep(cook_time)  # 요리 시간 시뮬레이션
    print(f"✅ {dish_name} 요리 완료! ({cook_time}초 소요)")
    return f"{dish_name} 완성"

async def prepare_multiple_dishes():
    """여러 요리를 동시에 준비하는 함수"""
    print("🏪 레스토랑 오픈! 여러 요리를 동시에 준비합니다.\n")
    
    start_time = time.time()
    
    # 여러 요리를 동시에 시작 (gather 사용)
    results = await asyncio.gather(
        cook_dish("파스타", 3),
        cook_dish("스테이크", 5), 
        cook_dish("샐러드", 2),
        cook_dish("수프", 4)
    )
    
    end_time = time.time()
    total_time = end_time - start_time
    
    print(f"\n🎉 모든 요리 완료!")
    print(f"📋 완성된 요리: {', '.join(results)}")
    print(f"⏱️  총 소요 시간: {total_time:.1f}초")
    print(f"💡 순차 처리시 예상 시간: {3+5+2+4}초")

# 실행 함수
async def main():
    await prepare_multiple_dishes()

# 프로그램 실행
if __name__ == "__main__":
    asyncio.run(main())

💻 실행 결과 예시:

🏪 레스토랑 오픈! 여러 요리를 동시에 준비합니다.

🍳 파스타 요리 시작!
🍳 스테이크 요리 시작!
🍳 샐러드 요리 시작!
🍳 수프 요리 시작!
✅ 샐러드 요리 완료! (2초 소요)
✅ 파스타 요리 완료! (3초 소요)
✅ 수프 요리 완료! (4초 소요)
✅ 스테이크 요리 완료! (5초 소요)

🎉 모든 요리 완료!
📋 완성된 요리: 파스타 완성, 스테이크 완성, 샐러드 완성, 수프 완성
⏱️  총 소요 시간: 5.0초
💡 순차 처리시 예상 시간: 14초

예제 2 간단한 비동기 웹 요청 (시뮬레이션)

📦 참고: 실제 웹 요청을 위해서는 aiohttp 라이브러리 설치가 필요합니다. (pip install aiohttp)

import asyncio
import random
import time

async def fetch_url_simulation(url, delay=None):
    """웹 URL을 가져오는 시뮬레이션 함수"""
    if delay is None:
        delay = random.uniform(0.5, 2.0)  # 0.5~2초 랜덤 지연
    
    print(f"🌐 {url} 요청 시작...")
    await asyncio.sleep(delay)  # 네트워크 지연 시뮬레이션
    
    # 응답 데이터 시뮬레이션
    response_data = f"Data from {url} (응답시간: {delay:.1f}초)"
    print(f"✅ {url} 응답 완료!")
    return response_data

async def batch_fetch_urls():
    """여러 URL을 동시에 요청하는 함수"""
    urls = [
        "https://api.example1.com/data",
        "https://api.example2.com/users", 
        "https://api.example3.com/posts",
        "https://api.example4.com/products",
        "https://api.example5.com/analytics"
    ]
    
    print(f"📡 {len(urls)}개 API 동시 호출 시작!\n")
    start_time = time.time()
    
    # 모든 URL을 동시에 요청
    try:
        responses = await asyncio.gather(
            *[fetch_url_simulation(url) for url in urls],
            return_exceptions=True  # 예외가 발생해도 계속 진행
        )
        
        end_time = time.time()
        
        print(f"\n📊 결과 요약:")
        print(f"⏱️  총 소요 시간: {end_time - start_time:.1f}초")
        print(f"📈 성공한 요청: {len([r for r in responses if not isinstance(r, Exception)])}개")
        
        # 각 응답 출력
        for i, response in enumerate(responses, 1):
            if isinstance(response, Exception):
                print(f"❌ 요청 {i}: 오류 발생 - {response}")
            else:
                print(f"📄 응답 {i}: {response}")
                
    except Exception as e:
        print(f"❗ 전체 작업 오류: {e}")

# 실제 aiohttp를 사용한 예제 (참고용)
async def real_web_request_example():
    """실제 aiohttp를 사용한 웹 요청 예제 (참고용 - 주석 처리)"""
    # import aiohttp
    # 
    # async with aiohttp.ClientSession() as session:
    #     tasks = []
    #     urls = ["https://httpbin.org/delay/1", "https://httpbin.org/delay/2"]
    #     
    #     for url in urls:
    #         task = asyncio.create_task(fetch_real_url(session, url))
    #         tasks.append(task)
    #     
    #     responses = await asyncio.gather(*tasks)
    #     return responses
    
    print("💡 실제 웹 요청을 원한다면 aiohttp를 설치하고 위의 주석을 해제하세요!")

async def main():
    print("=== 비동기 웹 요청 시뮬레이션 ===\n")
    await batch_fetch_urls()
    print("\n" + "="*50)
    await real_web_request_example()

# 프로그램 실행
if __name__ == "__main__":
    asyncio.run(main())

💻 실행 결과 예시:

📡 5개 API 동시 호출 시작!

🌐 https://api.example1.com/data 요청 시작...
🌐 https://api.example2.com/users 요청 시작...
🌐 https://api.example3.com/posts 요청 시작...
🌐 https://api.example4.com/products 요청 시작...
🌐 https://api.example5.com/analytics 요청 시작...
✅ https://api.example1.com/data 응답 완료!
✅ https://api.example3.com/posts 응답 완료!
✅ https://api.example5.com/analytics 응답 완료!
✅ https://api.example2.com/users 응답 완료!
✅ https://api.example4.com/products 응답 완료!

📊 결과 요약:
⏱️  총 소요 시간: 1.8초
📈 성공한 요청: 5개

핵심 학습 포인트

1

asyncio.gather()로 여러 작업을 동시에 실행

2

I/O 바운드 작업에서 극적인 성능 향상 가능

3

async/await 패턴으로 읽기 쉬운 비동기 코드 작성

4

예외 처리와 함께 안정적인 비동기 프로그램 구현

 

마무리

이번 강의에서는 파이썬의 **비동기 프로그래밍 핵심 개념**들을 학습했습니다. asyncio 라이브러리와 async/await 키워드를 활용하여 I/O 바운드 작업의 성능을 극적으로 개선하는 방법을 다뤘습니다.

✅ 학습한 핵심 내용

  • • 이벤트 루프와 코루틴의 개념
  • • async/await 키워드 사용법
  • • 동시 실행을 통한 성능 최적화
  • • 실용적인 비동기 코드 패턴

🎯 활용 분야

  • • 웹 API 다중 호출
  • • 파일 시스템 I/O 작업
  • • 데이터베이스 쿼리 최적화
  • • 실시간 데이터 처리

다음 강의에서는 **병행성과 병렬성의 차이점**을 살펴보고, threadingmultiprocessing 모듈을 활용한 더 고급 동시성 프로그래밍 기법을 학습하겠습니다. GIL(Global Interpreter Lock)의 개념도 함께 다룰 예정입니다.

728x90
반응형