비동기 프로그래밍 - 1. 개념과 코루틴 이해하기

박병현·2024년 6월 23일
0

비동기 프로그래밍 - 1. 개념과 코루틴 이해하기

1. 서론

비동기 프로그래밍이란?

비동기 프로그래밍은 프로그램의 작업이 완료될 때까지 기다리지 않고, 다른 작업을 병렬로 처리하는 기법입니다. 이는 특히 I/O 바운드 작업에서 효율적이며, 네트워크 요청, 파일 입출력 등 대기 시간이 발생하는 작업에서 유용합니다.

동기 vs 비동기 프로그래밍
  • 동기 프로그래밍: 요청을 보내면 결과를 받을 때까지 기다리는 방식입니다. 모든 작업이 순차적으로 실행되므로 간단하지만, 비효율적일 수 있습니다. 동기 프로그래밍에서는 이벤트를 처리하는 동안 이벤트 핸들러가 블록킹됩니다.
sequenceDiagram
    participant Client
    participant Server
    Client->>Server: Make Request
    Server-->>Client: Waiting for Response
    Client->>Server: Blocking
    Server-->>Client: Response Received

위 그림은 동기 프로그래밍에서의 블록킹을 설명합니다. 클라이언트가 서버에 요청을 보내면, 서버의 응답을 받을 때까지 클라이언트는 다른 작업을 수행하지 못하고 대기합니다.

  • 비동기 프로그래밍: 요청을 보내고, 결과를 기다리는 동안 다른 작업을 수행합니다. 여러 작업이 동시에 진행될 수 있어 효율적입니다. 비동기 프로그래밍에서는 이벤트 핸들러가 블록킹되지 않으며, 이벤트 루프가 여러 이벤트를 관리하고 처리합니다.
sequenceDiagram
    participant Client
    participant Server
    Client->>Server: Make Request
    Client-->>Client: Continue Working - Non-Blocking
    Server-->>Server: EventLoop - 이벤트 루프에서 작업 대기
    Server-->>Client: Response Ready -이벤트 루프에서 응답 준비
    Client-->>Client: Get Response and do something

위 그림은 비동기 프로그래밍에서 클라이언트가 요청을 보낸 후, 이벤트 루프가 서버의 응답을 기다리고 클라이언트가 다른 작업을 수행할 수 있음을 보여줍니다. 이벤트 루프는 서버의 응답이 준비되면 클라이언트에게 이를 전달하여 처리하게 합니다.

이벤트 루프의 역할

Python의 asyncio는 이벤트 드리븐(event-driven) 모델을 기반으로 합니다. 이벤트 드리븐 모델은 프로그램의 흐름을 사전 정의된 일련의 이벤트와 해당 이벤트를 처리할 핸들러에 의해 결정하는 모델입니다. asyncio는 비동기 I/O 작업을 효율적으로 처리하기 위해 이벤트 루프(event loop)를 사용합니다. 이벤트 루프는 비동기 프로그래밍의 핵심 요소로, 여러 비동기 작업을 관리하고 스케줄링합니다. 이벤트 루프는 각 작업이 완료될 때까지 기다리지 않고, 다른 작업을 계속해서 처리합니다.

2. 비동기 프로그래밍의 장점

성능 향상

비동기 프로그래밍은 동일한 시간 내에 더 많은 작업을 처리할 수 있어 성능이 크게 향상됩니다. 특히, 네트워크나 파일 I/O 작업에서 대기 시간이 줄어듭니다.

응답성 개선

사용자 요청에 대한 응답 시간이 줄어들어, 시스템의 효율성과 반응 속도가 높아집니다.

MSA와의 시너지

마이크로서비스 아키텍처(MSA)는 비동기 프로그래밍과 잘 어울리는 아키텍처입니다. 각 서비스가 독립적으로 동작하며, 서로 비동기적으로 통신할 수 있기 때문입니다. 비동기 프로그래밍을 통해 MSA는 더 높은 확장성과 성능을 제공할 수 있습니다.

3. 비동기 프로그래밍의 단점

복잡성 증가

비동기 프로그래밍은 동기 프로그래밍에 비해 복잡합니다. 코드의 흐름이 직관적이지 않아 디버깅과 유지보수가 어려울 수 있습니다. 또한, 서비스 간 비동기 통신이 복잡해질 수 있으며, 분산된 데이터 관리로 인해 데이터 일관성을 유지하기 어려울 수 있습니다.

4. Python에서 비동기 프로그래밍의 역사

asyncio 이전

Python에서는 비동기 프로그래밍을 위해 주로 threadingmultiprocessing 라이브러리를 사용했습니다. 하지만 이는 복잡하고, 성능상의 제약이 있었습니다.

asyncio 이후

Python 3.4에 도입된 asyncio 라이브러리는 비동기 프로그래밍을 더 쉽게 하고, 고성능을 제공합니다. asyncawait 키워드를 통해 코루틴을 사용하여 비동기 프로그래밍을 구현할 수 있습니다.

5. 코루틴이란?

정의와 개념

코루틴은 함수와 유사하지만, 실행을 중단하고 다시 시작할 수 있는 특수한 함수입니다. Python에서는 async def로 코루틴을 정의합니다.

일반 함수와의 차이점

일반 함수는 호출되면 바로 실행되고, 완료될 때까지 중단되지 않습니다. 반면, 코루틴은 실행 도중 await 키워드를 만나면 다른 작업을 수행할 수 있게 실행을 중단합니다.

6. 코루틴의 기본 사용법

async 키워드

async 키워드는 코루틴을 정의하는 데 사용됩니다. 이를 통해 생성된 함수는 일반 함수와는 달리, 비동기적으로 실행될 수 있습니다.

await 키워드

await 키워드는 코루틴 내에서 다른 코루틴이나 비동기 함수를 호출할 때 사용됩니다. 이는 해당 작업이 완료될 때까지 코루틴의 실행을 중단하고, 완료되면 다시 실행을 재개합니다.

7. 코루틴의 동작 원리

코루틴 객체

코루틴 함수는 호출되면 코루틴 객체를 반환합니다. 이 객체는 실제로 실행되지 않으며, await 키워드를 통해 실행이 시작됩니다.

코루틴의 상태 전환

코루틴은 여러 상태를 가질 수 있습니다. 생성된 상태에서 시작되어, 실행 중, 대기 중, 완료 등의 상태로 전환됩니다.

graph LR;
    A[생성됨] --> B[실행 중];
    B --> C[대기 중];
    C --> D[재개];
    D --> E[완료];

8. 코루틴의 예제와 실습

간단한 예제

아래는 기본적인 코루틴의 예제입니다. async def로 코루틴을 정의하고, await를 사용하여 다른 코루틴을 호출합니다.

import asyncio

async def say_hello():
    print("Hello")
    await asyncio.sleep(1)  # 1초 대기
    print("World")

# 이벤트 루프를 통해 코루틴 실행
asyncio.run(say_hello())

이 예제에서 say_hello 함수는 코루틴으로 정의되어 있으며, await asyncio.sleep(1) 문장을 만나면 1초 동안 대기합니다. 이 동안 다른 작업을 수행할 수 있습니다.

이벤트 루프의 역할

asyncio.run(say_hello())는 이벤트 루프를 시작하고, say_hello 코루틴을 실행합니다. 이벤트 루프는 여러 코루틴을 관리하며, 각각의 코루틴이 대기 상태에 있을 때 다른 코루틴을 실행할 수 있게 합니다.

graph TD;
    A[이벤트 루프 시작] --> B[say_hello 실행];
    B --> C[Hello 출력];
    C --> D[1초 대기 (await)];
    D --> E[다른 작업 실행 가능];
    E --> F[1초 후 World 출력];
    F --> G[코루틴 완료 및 이벤트 루프 종료];

결론

비동기 프로그래밍은 현대 소프트웨어 개발에서 중요한 역할을 합니다. Python의 asyncio 라이브러리와 코루틴을 사용하면 효율적인 비동기 프로그램을 작성할 수 있습니다. 이러한 개념을 깊이 이해하고 활용하면, 더 나은 성능과 응답성을 가진 애플리케이션을 개발할 수 있습니다.

FastAPI와 비동기 프로그래밍

FastAPI는 비동기 프로그래밍을 자연스럽게 지원하는 웹 프레임워크입니다. FastAPI는 비동기 엔드포인트를 쉽게 정의할 수 있으며, 이를 통해 높은 성능을 유지할 수 있습니다.

FastAPI의 비동기 엔드포인트 예제

아래는 FastAPI를 사용하여 비동기 엔드포인트를 정의하는 간단한 예제입니다.

from fastapi import FastAPI

app = FastAPI()

@app.get("/async")
async def async_endpoint():
    await asyncio.sleep(1)  # 1초 대기
    return {"message": "This is an async endpoint"}

이 예제에서 /async 엔드포인트는 비동기적으로 동작하며, 요청을 처리하는 동안 1초 동안 대기합니다. 이러한 비동기 엔드포인트는 FastAPI의 이벤트 루프에서 관리됩니다.

profile
AI Application Engineer

0개의 댓글