프로그래밍/Python

병행 및 병렬 처리: Python의 async/await 완벽 가이드

shimdh 2025. 2. 27. 10:13
728x90

현대 프로그래밍에서 병행(Concurrency)병렬(Parallelism) 처리는 애플리케이션의 성능을 최적화하고 응답 속도를 향상시키는 중요한 요소입니다. 특히 Python에서는 비동기 프로그래밍(Asynchronous Programming) 을 통해 I/O 작업을 보다 효율적으로 수행할 수 있습니다.

이 글에서는 Python의 asyncawait 키워드를 활용한 비동기 프로그래밍의 개념, 동작 방식, 주요 예제 및 활용 사례까지 심층적으로 다뤄보겠습니다.


1. 비동기 프로그래밍이란?

🔹 동기(Synchronous) vs 비동기(Asynchronous)

비동기 프로그래밍을 이해하기 위해서는 동기(Synchronous) 프로그래밍과의 차이를 먼저 살펴보는 것이 중요합니다.

  • 동기 방식: 한 번에 하나의 작업만 실행되며, 하나의 작업이 끝나야 다음 작업을 수행할 수 있음.
  • 비동기 방식: 하나의 작업을 실행하는 동안 다른 작업을 동시에 수행할 수 있음.

🔹 비동기 프로그래밍의 필요성

  • 네트워크 요청, 데이터베이스 쿼리, 파일 읽기/쓰기 등의 I/O 바운드 작업에서는 응답을 기다리는 시간이 길어질 수 있음.
  • 비동기 처리를 사용하면 CPU가 유휴 상태로 있는 시간을 줄이고, 다른 작업을 동시에 실행하여 성능을 최적화할 수 있음.
  • 기존의 멀티스레딩보다 가볍고 효율적인 방식으로 동시성을 구현할 수 있음.

2. Python의 async와 await

Python에서는 asyncawait 키워드를 사용하여 비동기 프로그래밍을 구현할 수 있습니다.

🔹 async 키워드

  • async 키워드는 비동기 함수(코루틴, Coroutine) 를 정의할 때 사용합니다.
  • 일반적인 함수(def) 대신 async def를 사용하면 해당 함수가 비동기로 동작합니다.
  • 비동기 함수는 실행할 때 즉시 실행되지 않고, 코루틴 객체를 반환합니다.

🔹 await 키워드

  • await 키워드는 비동기 함수 내에서 다른 비동기 작업을 호출하고, 완료될 때까지 대기하는 역할을 합니다.
  • await을 사용하면 현재 실행 중인 작업이 끝날 때까지 블로킹(blocking) 없이 다른 코루틴이 실행될 수 있도록 함.

🔹 기본 예제: asyncawait의 동작 방식

import asyncio

async def say_hello():
    print("안녕하세요!")
    await asyncio.sleep(2)  # 2초 동안 기다림 (비동기 대기)
    print("다시 만나요!")

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

✅ 실행 결과:

안녕하세요!
(2초 후)
다시 만나요!

이 코드에서 asyncio.sleep(2)CPU를 차단하지 않고, 비동기적으로 2초 동안 기다리는 역할을 합니다.


3. 여러 개의 비동기 작업 실행하기

비동기 프로그래밍의 장점은 여러 개의 작업을 동시에 실행할 수 있다는 것입니다.

🔹 asyncio.gather()를 활용한 병렬 실행

import asyncio

async def task(name, delay):
    print(f"{name} 시작")
    await asyncio.sleep(delay)
    print(f"{name} 완료")

async def main():
    tasks = [
        task("작업 1", 3),
        task("작업 2", 2),
        task("작업 3", 1),
    ]

    await asyncio.gather(*tasks)  # 모든 작업을 동시에 실행하고 완료될 때까지 기다림

asyncio.run(main())

✅ 실행 결과:

작업 1 시작
작업 2 시작
작업 3 시작
작업 3 완료
작업 2 완료
작업 1 완료

여기서 asyncio.gather(*tasks)를 사용하면 모든 작업을 동시에 실행할 수 있으며, 개별 작업이 완료될 때마다 처리됩니다.


4. 실전 예제: 웹 크롤링 (aiohttp 활용)

비동기 프로그래밍은 웹 스크래핑, API 요청, 실시간 데이터 스트리밍 등의 작업에서 특히 유용합니다.

🔹 여러 개의 URL에서 데이터를 비동기적으로 가져오기

import asyncio
import aiohttp

async def fetch_data(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def main():
    urls = [
        'https://example.com',
        'https://example.org',
        'https://example.net'
    ]

    tasks = [fetch_data(url) for url in urls]

    # 모든 태스크를 동시에 실행하고 결과를 기다림
    results = await asyncio.gather(*tasks)

    for i, result in enumerate(results):
        print(f"사이트 {i+1}: {result[:100]}...")  # 응답의 처음 100자만 출력

asyncio.run(main())

✅ 실행 결과:

사이트 1: <html><head><title>Example Domain</title>...
사이트 2: <html><head><title>Example Domain</title>...
사이트 3: <html><head><title>Example Domain</title>...

이 코드는 여러 개의 웹사이트에서 데이터를 동시에 가져오는 비동기 웹 크롤러로, aiohttp 라이브러리를 활용하여 비동기적으로 HTTP 요청을 보냅니다.


5. 비동기 프로그래밍의 장점과 한계

✅ 장점

  • I/O 바운드 작업(네트워크 요청, 데이터베이스 쿼리 등)에서 성능 최적화 가능.
  • 블로킹 없이 여러 작업을 동시에 실행하여 응답 속도 향상.
  • 코드가 직관적이고 가독성이 높음 (콜백 기반 코드보다 이해하기 쉬움).

❌ 한계

  • CPU 바운드 작업에는 적합하지 않음 (멀티프로세싱이 더 적합함).
  • 비동기 라이브러리를 사용해야 하므로 일부 동기 코드와 호환성이 떨어질 수 있음.
  • 디버깅이 어려울 수 있음 (스택 트레이스가 복잡할 수 있음).

🔥 결론: Python async/await을 활용한 효율적인 비동기 프로그래밍

Python의 async/await을 활용하면 비동기 작업을 효율적으로 실행하고, I/O 바운드 작업의 성능을 극대화할 수 있습니다. 이를 적절히 활용하면 웹 크롤링, API 요청, 실시간 데이터 처리 등 다양한 분야에서 성능 최적화를 이룰 수 있습니다.

🎯 핵심 정리

  • 비동기 프로그래밍은 동기 방식보다 더 효율적으로 작업을 처리할 수 있음.
  • asyncawait을 활용하면 여러 개의 작업을 동시에 실행할 수 있음.
  • asyncio.gather()를 사용하면 여러 비동기 작업을 병렬로 실행 가능.
  • I/O 바운드 작업에서는 비동기 프로그래밍이 멀티스레딩보다 효율적임.

Python 비동기 프로그래밍을 제대로 활용하면 더 빠르고 확장성이 뛰어난 애플리케이션을 개발할 수 있습니다. 🚀

728x90