포스트

[FastAPI] <1 : async/await>

참고 문서: 공식문서

0. 이 글에서 설명하고 있는 것들

  • 동시성과 병렬성
  • async / await
  • 코루틴

1. 동시성과 병렬성

먼저 async / await를 설명하기 전에 프로그래밍에서의 동시성과 병렬성을 설명해보자.

공식문서에 따르면 해당 개념을 여자친구와 버거를 먹으러 레스토랑에 간 상황에 비유하고 있다.

이 버거 비유에서 우리에게 필요한 작업들은 다음과 같다.

  1. 손님이 버거를 주문하기
  2. 점원이 버거를 주문받기
  3. 요리사가 버거를 요리하기
  4. 버거가 요리될 때까지 기다리기
  5. 기다리는 동안 여자친구와 대화하기
  6. 요리가 완료됐을 경우, 버거를 가지러오기.

1.1 동시 버거

  1. 동시 버거는 한 명의 점원과 한 명의 요리사가 있는 경우이다. 손님은 버거를 주문하기 위해 줄을 선다. 주문한다면 번호표를 발급한다.
  2. 점원에게 버거를 주문한다면, 점원이 요리사에게 주문을 전달한다. 요리사는 이미 내 앞에 있던 주문의 요리들을 수행하고 있다.
  3. 손님의 요리가 완료될 때까지 자리에 앉아 여자친구와 대화한다. (이 과정에서 손님은 요리를 대기하는 일과, 여자친구와 대화하는 일을 동시에 처리한다.)
  4. 요리가 완료되면 손님은 번호표가 있기 때문에 여자친구와의 대화 중간에 요리를 회수하는 작업을 하지 않아도 된다. 여자친구와의 대화가 끝난다면 요리를 회수한다.
  5. 버거를 먹는다.

1.2 병렬 버거

  1. 병렬 버거는 점원이면서 동시에 요리사인 여러명의 직원이 있다. 손님은 버거를 주문하기 위해 줄을 서서 기다린다.
  2. 이 과정에서 주문표는 없다. 점원이 동시에 요리사이기 때문에 주문을 받자마자 요리에 들어가기 때문이다.
  3. 손님 차례가 오면 버거를 주문하고 카운터 앞에서 기다린다. 손님은 주문표가 없기 때문에 자리를 떠날 수 없다. (해당 과정에서 여자친구와 대화는 수행할 수 없다.)
  4. 요리가 나온다면 요리를 가져와 먹는 일을 수행한다.

1.3 결론

이 이야기에서 주문을 받거나, 버거를 요리하거나, 여자친구와 대화하는 등 실제 수행해야 하는 일은 컴퓨터 상에서 CPU가 해야하는 연산을 뜻한다.

반대로 손님이 줄을 서거나, 요리를 기다리는 일 등은 CPU의 연산과 연산 사이 대기시간을 뜻한다.

개개인의 주문 대기시간은 굉장히 짧지만, 이것이 하나하나 모이게 된다면 정말 긴 대기시간이 될 것이다. 이 과정을 “병렬 버거” 의 방식으로 처리한다면 우리는 우리의 실제 연산이 수행되기 전까지 앞 사람들의 연산이 끝나는 것을 기다려야 한다.

그렇기 때문에 웹API에선 “동시 버거”의 방식을 사용한다. “동시 버거”의 방식에선 내 앞의 있는 주문을 기다리는 동시에 다른 일을 처리할 수 있다.

참고:

두 방식의 우열을 설명하는 글이 아니다. 각각 필요에 따라 수행하는 것이 좋다.

예를 들어 대기시간은 없지만 저택 곳곳을 청소해야 하는 일을 생각해보자. 이 경우엔 다수의 청소부가 있는 편이 실제 작업 시간을 줄이는 일이 될 것이다.

2. async / await

병렬 버거의 비유에서 우리는 점원과 손님의 1:1 관계를 설정해야 했다. 점원이 손님을 받고 요리하는 과정 안에서 다른 손님을 받을 수 없었다.

이 1:1 관계를 “동기화” 됐다고 표현한다. 우리의 웹API 개발에선 대기시간을 효율적으로 사용하기 위해 “비동기화” 된 코드를 사용해야만 하고, 그것을 python에서 가능케 하는 것이 asyncawait이다.

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 를 붙여줘야 한다. 또한 반드시 awaitasync def 내부에서만 사용 가능하다.

1
2
3
4
@app.get('/burgers')
async def read_burgers():
    burgers = await get_burgers(2)
    return burgers

3. 코루틴

async def 로 정의된 함수를 실행한다면 반드시 코루틴 객체로 반환된다.

코루틴은 함수의 진입점과 탈출점이 다양해 해당 로직들이 실행되는 와중에 자유롭게 멈추는 것이 가능하고, 다른 로직에 진입 한 후 다시 돌아와 멈춘 시점부터 진행하는 것이 가능하다.



이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.