[FastAPI] <1 : async/await>
0. 이 글에서 설명하고 있는 것들
- 동시성과 병렬성
- async / await
- 코루틴
1. 동시성과 병렬성
먼저 async / await를 설명하기 전에 프로그래밍에서의 동시성과 병렬성을 설명해보자.
공식문서에 따르면 해당 개념을 여자친구와 버거를 먹으러 레스토랑에 간 상황에 비유하고 있다.
이 버거 비유에서 우리에게 필요한 작업들은 다음과 같다.
- 손님이 버거를 주문하기
- 점원이 버거를 주문받기
- 요리사가 버거를 요리하기
- 버거가 요리될 때까지 기다리기
- 기다리는 동안 여자친구와 대화하기
- 요리가 완료됐을 경우, 버거를 가지러오기.
1.1 동시 버거
- 동시 버거는 한 명의 점원과 한 명의 요리사가 있는 경우이다. 손님은 버거를 주문하기 위해 줄을 선다. 주문한다면 번호표를 발급한다.
- 점원에게 버거를 주문한다면, 점원이 요리사에게 주문을 전달한다. 요리사는 이미 내 앞에 있던 주문의 요리들을 수행하고 있다.
- 손님의 요리가 완료될 때까지 자리에 앉아 여자친구와 대화한다. (이 과정에서 손님은 요리를 대기하는 일과, 여자친구와 대화하는 일을 동시에 처리한다.)
- 요리가 완료되면 손님은 번호표가 있기 때문에 여자친구와의 대화 중간에 요리를 회수하는 작업을 하지 않아도 된다. 여자친구와의 대화가 끝난다면 요리를 회수한다.
- 버거를 먹는다.
1.2 병렬 버거
- 병렬 버거는 점원이면서 동시에 요리사인 여러명의 직원이 있다. 손님은 버거를 주문하기 위해 줄을 서서 기다린다.
- 이 과정에서 주문표는 없다. 점원이 동시에 요리사이기 때문에 주문을 받자마자 요리에 들어가기 때문이다.
- 손님 차례가 오면 버거를 주문하고 카운터 앞에서 기다린다. 손님은 주문표가 없기 때문에 자리를 떠날 수 없다. (해당 과정에서 여자친구와 대화는 수행할 수 없다.)
- 요리가 나온다면 요리를 가져와 먹는 일을 수행한다.
1.3 결론
이 이야기에서 주문을 받거나, 버거를 요리하거나, 여자친구와 대화하는 등 실제 수행해야 하는 일은 컴퓨터 상에서 CPU가 해야하는 연산을 뜻한다.
반대로 손님이 줄을 서거나, 요리를 기다리는 일 등은 CPU의 연산과 연산 사이 대기시간을 뜻한다.
개개인의 주문 대기시간은 굉장히 짧지만, 이것이 하나하나 모이게 된다면 정말 긴 대기시간이 될 것이다. 이 과정을 “병렬 버거” 의 방식으로 처리한다면 우리는 우리의 실제 연산이 수행되기 전까지 앞 사람들의 연산이 끝나는 것을 기다려야 한다.
그렇기 때문에 웹API에선 “동시 버거”의 방식을 사용한다. “동시 버거”의 방식에선 내 앞의 있는 주문을 기다리는 동시에 다른 일을 처리할 수 있다.
참고:
두 방식의 우열을 설명하는 글이 아니다. 각각 필요에 따라 수행하는 것이 좋다.
예를 들어 대기시간은 없지만 저택 곳곳을 청소해야 하는 일을 생각해보자. 이 경우엔 다수의 청소부가 있는 편이 실제 작업 시간을 줄이는 일이 될 것이다.
2. async
/ await
병렬 버거의 비유에서 우리는 점원과 손님의 1:1 관계를 설정해야 했다. 점원이 손님을 받고 요리하는 과정 안에서 다른 손님을 받을 수 없었다.
이 1:1 관계를 “동기화” 됐다고 표현한다. 우리의 웹API 개발에선 대기시간을 효율적으로 사용하기 위해 “비동기화” 된 코드를 사용해야만 하고, 그것을 python에서 가능케 하는 것이 async
와 await
이다.
1
2
3
async def get_burgers(number: int):
# 버거가 만들어지는 시간 중에 다른 비동기화된 일을 할 수 있음.
return burgers
이렇게 def
앞에 async
를 붙이면 비동기 함수 사용이 가능하다.
1
2
3
def get_sequential_burgers(number: int):
# 버거가 만들어지는 시간 중에 다른 순차적인 일을 할 수 있음
return burgers
만약 async
를 사용해 비동기적으로 함수를 사용하고 싶으면 반드시 await
를 붙여줘야 한다. 또한 반드시 await
는 async def
내부에서만 사용 가능하다.
1
2
3
4
@app.get('/burgers')
async def read_burgers():
burgers = await get_burgers(2)
return burgers
3. 코루틴
async def
로 정의된 함수를 실행한다면 반드시 코루틴 객체로 반환된다.
코루틴은 함수의 진입점과 탈출점이 다양해 해당 로직들이 실행되는 와중에 자유롭게 멈추는 것이 가능하고, 다른 로직에 진입 한 후 다시 돌아와 멈춘 시점부터 진행하는 것이 가능하다.