Python/문법

[Python] 비동기(async) 프로그래밍 1

comgu 2025. 5. 22. 18:59
반응형

들어가며

파이썬으로 백엔드 개발을 시작하다 보면 async와 await 키워드를 자주 마주치게 된다. 특히 FastAPI 같은 현대적인 웹 프레임워크를 사용할 때 비동기 처리는 필수적인 개념이다. 하지만 많은 초보 개발자들이 비동기 프로그래밍을 어려워한다. 이 연재를 통해 비동기의 기본 개념부터 고급 활용법까지 차근차근 학습해보자.

 

동기 vs 비동기

동기(Synchronous) 처리

동기 처리는 마치 은행 창구에서 일을 처리하는 것과 같다. 한 번에 한 고객만 처리하며, 이전 고객의 업무가 완전히 끝나야 다음 고객을 받는다.

import time

def 은행업무_처리(고객명, 처리시간):
    print(f"{고객명} 업무 시작")
    time.sleep(처리시간)  # 업무 처리 시간 시뮬레이션
    print(f"{고객명} 업무 완료")

# 동기적 처리
시작시간 = time.time()
은행업무_처리("김철수", 3)
은행업무_처리("박영희", 2)
은행업무_처리("이민수", 1)
총시간 = time.time() - 시작시간
print(f"총 처리 시간: {총시간:.1f}초")

위 코드의 실행 결과는 다음과 같다:

김철수 업무 시작
김철수 업무 완료
박영희 업무 시작
박영희 업무 완료
이민수 업무 시작
이민수 업무 완료
총 처리 시간: 6.0초

비동기(Asynchronous) 처리

비동기 처리는 여러 창구가 있는 은행과 같다. 각 창구에서 동시에 여러 고객의 업무를 처리할 수 있다.

import asyncio

async def 비동기_은행업무_처리(고객명, 처리시간):
    print(f"{고객명} 업무 시작")
    await asyncio.sleep(처리시간)  # 비동기적 대기
    print(f"{고객명} 업무 완료")

async def main():
    시작시간 = time.time()
    
    # 여러 업무를 동시에 처리
    await asyncio.gather(
        비동기_은행업무_처리("김철수", 3),
        비동기_은행업무_처리("박영희", 2),
        비동기_은행업무_처리("이민수", 1)
    )
    
    총시간 = time.time() - 시작시간
    print(f"총 처리 시간: {총시간:.1f}초")

# 비동기 함수 실행
asyncio.run(main())

실행 결과:

김철수 업무 시작
박영희 업무 시작
이민수 업무 시작
이민수 업무 완료
박영희 업무 완료
김철수 업무 완료
총 처리 시간: 3.0초

 

비동기가 필요한 이유

1. I/O 대기 시간 최적화

웹 개발에서 가장 많은 시간을 소모하는 작업은 다음과 같다:

  • 데이터베이스 쿼리
  • 외부 API 호출
  • 파일 읽기/쓰기
  • 네트워크 통신

이러한 작업들은 CPU가 실제로 연산을 하는 시간보다 응답을 기다리는 시간이 훨씬 길다.

2. 서버 성능 향상

동기 처리 방식에서는 한 번에 하나의 요청만 처리할 수 있다. 하지만 비동기 처리를 사용하면 여러 요청을 동시에 처리할 수 있어 서버의 처리량(throughput)이 크게 향상된다.

# 동기 방식 - 비효율적
def get_user_data(user_id):
    # 데이터베이스 조회 (1초 소요)
    time.sleep(1)
    return {"user_id": user_id, "name": "홍길동"}

# 비동기 방식 - 효율적
async def get_user_data_async(user_id):
    # 비동기 데이터베이스 조회 (1초 소요)
    await asyncio.sleep(1)
    return {"user_id": user_id, "name": "홍길동"}

 

비동기 프로그래밍의 핵심 개념

1. 이벤트 루프(Event Loop)

이벤트 루프는 비동기 프로그램의 심장이다. 마치 교통 신호등 제어 시스템처럼 여러 작업들을 순서대로 관리하고 실행한다.

2. 코루틴(Coroutine)

async def로 정의된 함수를 코루틴이라고 한다. 코루틴은 실행 중간에 다른 작업에게 제어권을 양보할 수 있는 특별한 함수다.

3. await 키워드

await는 "이 작업이 완료될 때까지 기다리되, 기다리는 동안 다른 작업을 처리해도 된다"는 의미다.

 

간단한 비동기 예제

웹 크롤링을 예로 들어보자:

import asyncio
import aiohttp
import time

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

async def main():
    urls = [
        'https://httpbin.org/delay/1',
        'https://httpbin.org/delay/2',
        'https://httpbin.org/delay/1'
    ]
    
    시작시간 = time.time()
    
    async with aiohttp.ClientSession() as session:
        # 모든 URL을 동시에 요청
        results = await asyncio.gather(*[
            fetch_url(session, url) for url in urls
        ])
    
    총시간 = time.time() - 시작시간
    print(f"총 처리 시간: {총시간:.1f}초")
    print(f"처리된 응답 수: {len(results)}")

asyncio.run(main())

이 코드는 3개의 HTTP 요청을 동시에 처리하여 전체 실행 시간을 크게 단축시킨다.

 

비동기를 사용해야 하는 경우

  1. 웹 서버 개발: FastAPI, Django 등에서 높은 동시성이 필요한 경우
  2. 데이터베이스 작업: MongoDB, PostgreSQL 등과의 비동기 통신
  3. 외부 API 호출: 여러 API를 동시에 호출해야 하는 경우
  4. 파일 I/O: 대용량 파일 처리나 여러 파일 동시 처리
  5. 실시간 통신: WebSocket, MQTT 등을 사용하는 경우

 

비동기를 사용하지 말아야 하는 경우

  1. CPU 집약적 작업: 복잡한 수학 연산, 이미지 처리 등
  2. 단순한 스크립트: 복잡도 대비 성능 향상이 미미한 경우
  3. 동기 라이브러리만 사용: 비동기 지원이 없는 라이브러리를 주로 사용하는 경우

 

마무리

비동기 프로그래밍은 현대 웹 개발의 필수 기술이다. 특히 FastAPI와 같은 프레임워크를 사용할 때 비동기의 이해는 선택이 아닌 필수다. 다음 편에서는 파이썬의 async와 await 키워드를 자세히 살펴보고, 실제 코드에서 어떻게 사용하는지 알아보겠다.

 

다음 편 예고

2편에서는 다음 내용을 다룰 예정이다:

  • async def와 일반 함수의 차이점
  • await 키워드의 동작 원리
  • 코루틴 객체와 이벤트 루프의 관계
  • 간단한 비동기 함수 작성 실습
반응형