이론적인 내용 정리는 아래를 참고
2022.06.03 - [Backend MLOps/Fastapi] - [Fastapi] asyncio 제대로 써보기 with pytest - 1
거두절미하고 파이썬 비동기처리의 기본은 yield 라고 할 수 있다.
함수에 yield를 사용함으로 제너레이터를 만들 수 있다.
yield는 return처럼 어떤 값을 반환하는 타이밍을 지정하는데
__next__ 메소드가 호출되기 전에는 다음 값을 반환하지 않고 기다린다.
즉 이 함수가 기다리는 동안 다른 함수들을 실행할 수 있다는 말이다.
이게 파이썬 비동기의 기초라고 할 수 있다.
이게 뭐가 비동기냐 라고 할 수 있는데
대충 asyncio의 비동기 루프를 따라해 보면 이렇게 된다.
이렇게 여러개의 제너래이터를 만들고 계속해서 next를 호출하는 함수를 만들면 어떨까?
결과는 아래와 같다.
함수들이 동시에 처리되는 것 처럼 보인다.
하지만 완벽한 비동기 처리는 아니다.
io의 지연이 있는지, 응답을 받은 상태인지 등을 알려주는 인디케이터가 없어서
각각의 next는 동기로 처리된다.
대충 감을 잡았으니 이제는 asyncio를 본격적으로 써보자.
최대한 위의 코드와 동일하게 짜보았다.
이때는 async def를 이용해서 함수를 짰는데
중간에 await도 하나 보인다.
실행해보자.
코루틴 오브젝트라는것이 생성되었다.
처음에 많이 헷갈렸던 부분은 async def 로 선언한 함수도 일종의 함수가 아닐까 하는 것이었다.
하지만 def와 async def는 엄연히 다르기 때문에 생성하는 객체도 완전히 다르다.
async def를 통해 생성한 객체는 실행 결과가 아니라 coroutine 객체를 반환한다.
위의 예시로 보면 generator를 반환한다고 보면 된다.
그러면 next와 같은 실행자를 이용해서 실행시킬 수 있어야하는데
이때 필요한 것이 await 키워드다.
await는 함수가 끝날때 까지 기다려라 라는 의미가 되기 때문에
await을 붙이면 동기처리되면서 함수의 실행값을 받을 수 있다.
그래서 코루틴 객체인 asynio.sleep 함수는 await이 필요하고 그렇지 않으면 코루틴 에러가 발생한다.
코루틴은 중요한 세가지 포인트가 있는데 진입점, 분기점, 탈출점 이다. (정확한 용어인지는 알 수 없다.)
동기 처리되던 도중 비동기로 처리할수 있는 일들이 뒤에 있다면 비동기로 분기하여
여러 작업을 (거의)동시에 수행하고 수행이 끝난 결과는 하나로 모아 다시 동기 처리를 진행한다.
이때 분기점에 해당하는 키워드가 async def 가 되고 탈출점에 해당하는 키워드가 await이 된다.
그러면 진입점은 어떻게 설정할 수 있냐하면
분기점에서 갈라져나가는 테스크들을 모두 모아서
코루틴 위에 얹고 테스크들을 관리하면서 비동기 처리를 발생시킨다.
즉 시작점에서는 async def로 분기되는 코루틴 객체를 모으고 시작점에 배치하여
출발을 알리는 신호탄을 쏘아야 비동기 작업이 시작된다.
위와 같이 main 함수에서 비동기 자원을 모으고
asyncio.run으로 비동기 자원들을 출발시킨다.
그러면 이렇게 (거의)동시에 처리되는 것을 볼 수 있다.
그리고 main역시 코루틴 객체임을 알 수 있는데
asyncio.run을 이용하면 코루틴 객체를 동기처리로 실행할 수 있음을 알 수 있다.
비동기 프레임 워크상에서 내가 짠 코루틴 객체들만 따로 비동기 처리를 하고
그 비동기 처리 묶음을 다른 함수들과는 동기처리해야 할 때가 있다.
그때는 asyncio.run를 이용해서 즉각 모아진 코루틴들을 실행할 수 있다.
비교하기 위해서 동기함수를 짜보았다.
이름과 출력횟수를 받으면 이름을 출력하고 1초 기다리고 넘어가는 작업을 횟수만큼 하는 함수다.
이번엔 동일한 수행을 비동기로 짜 보았다.
역시 출력횟수를 입력받으면 횟수만큼 1초를 쉬고 넘어간다.
이제 이 함수들을 아래와 같이 실행해 본다.
알파벳 수의 3배만큼 시간이 걸릴 것이다.
이건 동기함수의 출력이다.
아주 질서있게 순서대로 출력된 것을 볼 수 있다.
78초 걸렸다.
이건 비동기처리 결과다.
어떤 순서로 처리되었는지 알기 힘들다.
놀랍게도 하나의 테스크가 수행하는 만큼의 지연시간인 3초가 걸렸다.
이렇게 `지연 시간`이 걸리는 함수에서는 비동기 처리가 굉장한 이득을 볼 수 있고
지연시간이 오래 걸리는 부분은 io-job에 해당한다.
따라서 응답을 기다리는 io-job의 경우 비동기처리를 수행하는 것이 굉장히 유리하다.
get_event_loop를 이용해서 실행하는 방법도 있고
이렇게 실행하는 것도 가능하다.
이런 방식은 동기처리작업에 끼워넣을때 보기 좋다.
'삽질 > Python' 카테고리의 다른 글
[ Python 삽질 ] 파이썬 정규표현식 re.sub 을 이용해서 대문자 소문자 바꾸기 (0) | 2021.08.03 |
---|---|
[ Python 삽질 ] List의 원소가 한번에 다 바뀔때 deep copy? shallow copy? (2) | 2021.06.02 |
[ Python 삽질 ] __call__() got an unexpected keyword argument 해결법 (0) | 2020.12.29 |
댓글