본문으로 건너뛰기

Python 비동기 프로그래밍 시작하기: I/O 바운드 작업 효율화 가이드

Python의 asyncio 라이브러리를 활용하여 I/O 바운드 작업의 병렬 처리를 구현하고 세마포어와 에러 핸들링을 통해 확장성 있는 애플리케이션을 구축하는 방법을 다룹니다.

섹션별 상세

01
비동기 프로그래밍은 I/O 작업 중 발생하는 유휴 시간을 활용해 다른 작업을 수행하는 방식이다. async def로 코루틴을 정의하고 await로 비차단 대기를 수행하며, 이벤트 루프가 이러한 태스크들을 스케줄링하고 관리한다. 이는 원시 연산 속도를 높이는 것이 아니라 전체적인 처리량(Throughput)을 개선하는 데 목적이 있다.
02
asyncio.gather는 여러 코루틴을 동시에 실행하고 모든 작업이 완료될 때까지 기다린 후 결과를 수집한다. 결과값은 태스크가 전달된 순서대로 리스트 형태로 반환되며, 전체 실행 시간은 가장 오래 걸리는 단일 작업의 시간과 유사하게 단축된다. 이는 독립적인 네트워크 요청이나 DB 쿼리를 병렬로 처리할 때 매우 효율적이다.
python
import asyncio
import time

async def download_file(name, seconds):
    print(f"Starting {name}")
    await asyncio.sleep(seconds)
    print(f"Finished {name}")

async def main():
    start = time.perf_counter()
    await asyncio.gather(
        download_file("file-1", 2),
        download_file("file-2", 2),
        download_file("file-3", 2),
    )
    end = time.perf_counter()
    print(f"[TOTAL ASYNC] took {end - start:.4f} seconds")

asyncio.run(main())

asyncio.gather를 사용하여 여러 비동기 작업을 동시에 실행하고 전체 시간을 단축하는 예시

03
기존의 동기식 라이브러리(예: urllib)를 비동기 환경에서 사용할 때는 asyncio.to_thread를 활용해야 한다. 블로킹 작업을 별도 스레드에서 실행함으로써 메인 이벤트 루프가 멈추는 현상을 방지하고, 비동기 코드의 흐름을 유지하면서도 기존 동기 코드를 재사용할 수 있게 한다.
04
실무 환경에서는 외부 API의 속도 제한(Rate Limit)을 준수하기 위해 asyncio.Semaphore를 사용한다. 세마포어에 설정된 한도만큼만 태스크가 진입하도록 제어하여, 한 번에 너무 많은 요청이 발생해 시스템이 차단되거나 자원이 고갈되는 문제를 방지한다.
python
async def fetch(url):
    start = time.perf_counter()
    print(f"Fetching {url}")
    # Run blocking IO in a thread
    data = await asyncio.to_thread(fetch_sync, url)
    elapsed = time.perf_counter() - start
    print(f"Finished {url} in {elapsed:.2f} seconds")
    return data

asyncio.to_thread를 사용하여 동기식 블로킹 I/O 함수를 비동기 루프 내에서 안전하게 실행하는 방법

05
비동기 애플리케이션의 안정성을 위해 개별 태스크의 실패가 전체 워크플로우를 중단시키지 않도록 에러 핸들링 패턴을 적용한다. try-except 블록으로 예외를 캡처하고 구조화된 에러 응답을 반환함으로써, 일부 요청이 실패하더라도 나머지 성공한 작업의 결과를 안전하게 수집할 수 있다.
06
Jupyter Notebook 환경은 이미 활성화된 이벤트 루프를 가지고 있으므로 asyncio.run()을 사용할 수 없다. 대신 await main()과 같이 직접 코루틴을 호출해야 하며, 이러한 환경적 차이를 이해하는 것이 런타임 오류 방지에 중요하다.
python
semaphore = asyncio.Semaphore(2)

async def task(task_id):
    async with semaphore:
        print(f"Task {task_id} started")
        await asyncio.sleep(2)
        print(f"Task {task_id} finished")

asyncio.Semaphore를 활용하여 동시 실행 작업 수를 제한하는 동시성 제어 예시

용어 해설

이벤트 루프(Event Loop)
비동기 작업을 관리하고 실행하는 핵심 메커니즘이다. 대기 중인 태스크와 실행 준비가 된 태스크를 추적하며, I/O 대기가 발생할 때 실행 컨텍스트를 전환하여 동시성을 구현한다.
코루틴(Coroutine)
실행을 일시 중단했다가 나중에 재개할 수 있는 특수한 함수다. Python에서는 async def로 정의하며, 일반 함수와 달리 호출 시 즉시 실행되지 않고 코루틴 객체를 반환한다.
I/O 바운드(I/O Bound)
프로그램의 실행 속도가 CPU 연산보다 네트워크 응답, 데이터베이스 조회, 파일 입출력 등 외부 시스템과의 통신 대기 시간에 의해 결정되는 상태를 의미한다.
세마포어(Semaphore)
동시에 리소스에 접근할 수 있는 작업의 개수를 제한하는 동기화 객체다. API의 속도 제한(Rate Limit)을 준수하거나 시스템 자원 과부하를 방지하기 위해 사용된다.
블로킹 연산(Blocking Operation)
작업이 완료될 때까지 현재 실행 중인 스레드의 제어권을 넘기지 않고 멈춰 있게 만드는 작업이다. 비동기 환경에서 블로킹 연산이 발생하면 전체 이벤트 루프가 정지된다.

기술

  • Python
  • asyncio
  • urllib
  • Jupyter Notebook

활용 사례

  • Concurrent API Requests
  • Database Queries
  • Background Tasks
  • Web Scraping
AI 분석 전체 내용 보기

AI 요약 · 북마크 · 개인 피드 설정 — 무료

출처 · 인용 안내

원문 발행 2026. 03. 03.수집 2026. 03. 03.출처 타입 RSS

인용 시 "요약 출처: AI Trends (aitrends.kr)"를 표기하고, 사실 확인은 원문 보기 기준으로 진행해 주세요. 자세한 기준은 운영 정책을 참고해 주세요.