본문 바로가기
프로그래밍/파이썬

파이썬의 동시성 프로그래밍: asyncio와 코루틴 활용하기

by ennak 2024. 12. 7.
반응형

1. Intro

파이썬에서 동시성 프로그래밍은 복잡한 작업을 효율적으로 처리하는 강력한 도구다. 특히 asyncio 모듈과 코루틴을 활용하면 비동기 프로그래밍을 통해 I/O 바운드 작업의 성능을 크게 향상시킬 수 있다.

2. asyncio

asyncio는 파이썬 3.4부터 표준 라이브러리에 포함된 모듈로, 비동기 프로그래밍을 위한 프레임워크다. 이 모듈은 코루틴을 사용하여 동시성 코드를 작성할 수 있게 해주며, 이벤트 루프를 통해 비동기 작업을 관리한다.

asyncio의 주요 특징

  • 코루틴을 이용한 비동기 프로그래밍
  • 이벤트 루프 기반의 작업 관리
  • 네트워크 및 I/O 작업에 최적화
  • 동시성 프로그래밍을 위한 고수준 API 제공

3. 코루틴 (Coroutine)

코루틴은 asyncio의 핵심 개념이다. 일반 함수와 달리, 코루틴은 실행 중 중단되고 나중에 다시 재개될 수 있다. 이러한 특성 덕분에 I/O 작업 같은 블로킹 연산을 기다리는 동안 다른 작업을 수행할 수 있다.

async def my_coroutine():
    # 비동기 작업 수행
    await asyncio.sleep(1)
    return "완료"

'async def'로 정의된 함수가 코루틴이며, 'await' 키워드를 사용하여 다른 비동기 작업의 완료를 기다릴 수 있다.


4. 이벤트 루프 (Event Loop)

이벤트 루프는 asyncio의 중심 실행 메커니즘이다. 코루틴과 콜백을 관리하고 실행하는 역할을 한다. 이벤트 루프는 비동기 작업을 스케줄링하고, I/O 이벤트를 감지하며, 완료된 코루틴의 결과를 처리한다.

# 이벤트 루프 사용 예시
import asyncio

async def main():
    print("Hello")
    await asyncio.sleep(1)
    print("World")

asyncio.run(main())

asyncio.run()은 이벤트 루프를 생성하고 관리하는 고수준 API다.


5. 태스크 (Task)

태스크는 코루틴을 감싸는 객체로, 이벤트 루프에서 실행될 수 있게 한다. 태스크를 사용하면 여러 코루틴을 동시에 실행할 수 있다.

# 태스크 생성 및 실행
async def task1():
    await asyncio.sleep(1)
    return "Task 1 완료"

async def task2():
    await asyncio.sleep(2)
    return "Task 2 완료"

async def main():
    task_1 = asyncio.create_task(task1())
    task_2 = asyncio.create_task(task2())

    result1 = await task_1
    result2 = await task_2

    print(result1)
    print(result2)

asyncio.run(main())

6. 비동기 컨텍스트 매니저

asyncio는 비동기 컨텍스트 매니저를 지원한다. 이를 통해 리소스의 획득과 해제를 비동기적으로 관리할 수 있다.

class AsyncContextManager:
    async def __aenter__(self):
        print("Entering context")
        await asyncio.sleep(1)
        return self

    async def __aexit__(self, exc_type, exc_value, traceback):
        print("Exiting context")
        await asyncio.sleep(1)

async def main():
    async with AsyncContextManager() as manager:
        print("Inside context")

asyncio.run(main())

7. 비동기 이터레이터와 제너레이터

asyncio는 비동기 이터레이터와 제너레이터를 지원한다. 이를 통해 대량의 데이터를 비동기적으로 처리할 수 있다.

async def async_generator():
    for i in range(5):
        await asyncio.sleep(1)
        yield i

async def main():
    async for item in async_generator():
        print(item)

asyncio.run(main())

8. 동시성 vs 병렬성

asyncio는 동시성을 제공하지만, 병렬성과는 다르다. 동시성은 여러 작업을 번갈아가며 실행하는 것이고, 병렬성은 여러 작업을 실제로 동시에 실행하는 것이다. asyncio는 단일 스레드에서 동작하므로 CPU 바운드 작업보다는 I/O 바운드 작업에 더 적합하다.


9. 실제 사용 사례: 웹 스크래핑

asyncio를 활용한 웹 스크래핑 예제

import asyncio
import aiohttp

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

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

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

    for url, result in zip(urls, results):
        print(f"URL: {url}, Content length: {len(result)}")

asyncio.run(main())

이 예제에서는 여러 웹사이트의 내용을 동시에 가져온다. aiohttp 라이브러리를 사용하여 비동기 HTTP 요청을 수행한다.


10. 에러 처리

asyncio에서의 에러 처리는 일반적인 파이썬 예외 처리와 유사하지만, 비동기 컨텍스트를 고려해야 한다.

async def risky_operation():
    await asyncio.sleep(1)
    raise ValueError("An error occurred")

async def main():
    try:
        await risky_operation()
    except ValueError as e:
        print(f"Caught an error: {e}")

asyncio.run(main())

11. 디버깅

asyncio 프로그램의 디버깅은 일반 동기 프로그램보다 복잡할 수 있다. asyncio는 디버깅을 돕기 위한 몇 가지 도구를 제공한다.

  • asyncio.get_event_loop().set_debug(True): 이벤트 루프의 디버그 모드를 활성화한다.
  • PYTHONASYNCIODEBUG=1 환경 변수: 더 자세한 디버그 정보를 출력한다.

12. 성능 최적화

asyncio를 사용할 때 성능을 최적화하기 위한 몇 가지 팁

  • 큰 작업을 더 작은 비동기 작업으로 분할한다.
  • 불필요한 await 사용을 피한다.
  • asyncio.gather()를 사용하여 여러 코루틴을 동시에 실행한다.
  • CPU 바운드 작업은 별도의 프로세스나 스레드로 오프로드한다.

13. asyncio와 다른 라이브러리의 통합

많은 파이썬 라이브러리들이 asyncio와 호환되도록 설계되어 있다.

  • aiohttp: 비동기 HTTP 클라이언트/서버
  • asyncpg: 비동기 PostgreSQL 드라이버
  • motor: 비동기 MongoDB 드라이버

이러한 라이브러리들을 사용하면 데이터베이스 작업, 네트워크 통신 등을 비동기적으로 수행할 수 있다.

반응형