비동기 프로그래밍은 프로그램의 작업이 완료될 때까지 기다리지 않고, 다른 작업을 병렬로 처리하는 기법입니다. 이는 특히 I/O 바운드 작업에서 효율적이며, 네트워크 요청, 파일 입출력 등 대기 시간이 발생하는 작업에서 유용합니다.
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)를 사용합니다. 이벤트 루프는 비동기 프로그래밍의 핵심 요소로, 여러 비동기 작업을 관리하고 스케줄링합니다. 이벤트 루프는 각 작업이 완료될 때까지 기다리지 않고, 다른 작업을 계속해서 처리합니다.
비동기 프로그래밍은 동일한 시간 내에 더 많은 작업을 처리할 수 있어 성능이 크게 향상됩니다. 특히, 네트워크나 파일 I/O 작업에서 대기 시간이 줄어듭니다.
사용자 요청에 대한 응답 시간이 줄어들어, 시스템의 효율성과 반응 속도가 높아집니다.
마이크로서비스 아키텍처(MSA)는 비동기 프로그래밍과 잘 어울리는 아키텍처입니다. 각 서비스가 독립적으로 동작하며, 서로 비동기적으로 통신할 수 있기 때문입니다. 비동기 프로그래밍을 통해 MSA는 더 높은 확장성과 성능을 제공할 수 있습니다.
비동기 프로그래밍은 동기 프로그래밍에 비해 복잡합니다. 코드의 흐름이 직관적이지 않아 디버깅과 유지보수가 어려울 수 있습니다. 또한, 서비스 간 비동기 통신이 복잡해질 수 있으며, 분산된 데이터 관리로 인해 데이터 일관성을 유지하기 어려울 수 있습니다.
Python에서는 비동기 프로그래밍을 위해 주로 threading과 multiprocessing 라이브러리를 사용했습니다. 하지만 이는 복잡하고, 성능상의 제약이 있었습니다.
Python 3.4에 도입된 asyncio 라이브러리는 비동기 프로그래밍을 더 쉽게 하고, 고성능을 제공합니다. async와 await 키워드를 통해 코루틴을 사용하여 비동기 프로그래밍을 구현할 수 있습니다.
코루틴은 함수와 유사하지만, 실행을 중단하고 다시 시작할 수 있는 특수한 함수입니다. Python에서는 async def로 코루틴을 정의합니다.
일반 함수는 호출되면 바로 실행되고, 완료될 때까지 중단되지 않습니다. 반면, 코루틴은 실행 도중 await 키워드를 만나면 다른 작업을 수행할 수 있게 실행을 중단합니다.
async 키워드는 코루틴을 정의하는 데 사용됩니다. 이를 통해 생성된 함수는 일반 함수와는 달리, 비동기적으로 실행될 수 있습니다.
await 키워드는 코루틴 내에서 다른 코루틴이나 비동기 함수를 호출할 때 사용됩니다. 이는 해당 작업이 완료될 때까지 코루틴의 실행을 중단하고, 완료되면 다시 실행을 재개합니다.
코루틴 함수는 호출되면 코루틴 객체를 반환합니다. 이 객체는 실제로 실행되지 않으며, await 키워드를 통해 실행이 시작됩니다.
코루틴은 여러 상태를 가질 수 있습니다. 생성된 상태에서 시작되어, 실행 중, 대기 중, 완료 등의 상태로 전환됩니다.
graph LR;
A[생성됨] --> B[실행 중];
B --> C[대기 중];
C --> D[재개];
D --> E[완료];
아래는 기본적인 코루틴의 예제입니다. 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를 사용하여 비동기 엔드포인트를 정의하는 간단한 예제입니다.
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의 이벤트 루프에서 관리됩니다.