본문 바로가기
언어 정리/python_비동기관련_lib

코루틴 과 Eventloop 그리고 Future

by 알 수 없는 사용자 2022. 9. 14.

참고 :

https://tech.buzzvil.com/blog/asyncio-no-1-coroutine-and-eventloop/

 

asyncio 뽀개기 1 - Coroutine과 Eventloop

이 시리즈의 목적은 asyncio의 컴포넌트들과 활용법을 소개하는 것입니다. 최종적으로는 실제 production에 쓰이고 있는 graceful shutdown을 구현하는 것을 목표로 하며, 그 과정에서 필요한 asyncio 지식

tech.buzzvil.com

 

https://tech.buzzvil.com/blog/asyncio-no-2-future/

 

asyncio 뽀개기 2 - Future의 활용

Future를 잘 활용하면 단순히 await 하는 용도보다 더 다양한 흐름 제어를 할 수 있습니다. 이전 포스트에서는 asyncio의 핵심 컴포넌트인 코루틴과 Eventloop을 소개했습니다. 이번 포스트에서는 Future

tech.buzzvil.com

 

https://tech.buzzvil.com/blog/asyncio-no-3-sigterm/

 

asyncio 뽀개기 3 - SIGTERM (CTRL+C) 올바르게 처리하기

asyncio를 사용하는 서버라면 graceful shutdown을 할 수 있어야합니다. Eventloop에 task를 등록하는 구조이기 때문에 graceful shutdown을 하지 않으면 유저 혹은 다른 서버의 요청이 버려지는 현상이 발생할

tech.buzzvil.com

 

 


기본 지식이 필요합니다.

VS 구조, 정의 설명, 부분예시로 정리만 합니다.

저쪽에서 보는게 나을듯


 

1. asyncio.create_task(함수())  VS await 함수()

await 는 1. Eventloop에 등록 하고 2. Eventloop에 등록된 코루틴이 종료 될때 까지 wait(대기) 함

create_task 는 1. Eventloop에 등록만 해줌, 실행권을 Eventloop에 주지 않습니다.

 

2. asyncio.Task 객체 asyncio.future객체 관계

asyncio.create_task()가 반환하는 것은 Task 객체이고, 이 객체는 Future를 상속받기 때문에 동일하게 Awaitable합니다.

 

 

3. RuntimeWarning: coroutine 'run_job' was never awaited  에러 

코루틴 함수를 일반 함수처럼 호출시 나는 에러

이유 : 코루틴 함수는 코루틴 객체(Task)를 반환하므로 일반 함수처럼 실행 시킬 수 없다. 실행 시키는 방법은 Eventloop에 등록해서 사용할 수 밖에 없음.

 

4. 계층 구조 정리

await 키워드를 붙일 수 있는 최소 조건이 Awaitable입니다. 그래서 3가지 모두 await으로 실행이 끝나길 기다릴 수 있습니다. 하지만 코루틴은 Eventloop에 등록되지 않으면 실행되지 않기 때문에, await을 붙이거나 Future나 Task로 감싸야 합니다.

 

5. 비동기 객체로 캡슐화

코루틴 -> asyncio.Task 감싸는 방법     : task_1 = asyncio.create_task(코루틴함수()) 

코루틴 -> asyncio.Future 감싸는 방법  : fut_1   = loop.create_future(코루틴함수)

concurrent.futures.Future 객체를 asyncio.Future 객체로 감싸는 방법 : aync_fut_1 = asyncio.wrap_future(퓨처객체)

 

6. 프로세스 스케쥴러 VS Eventloop

선점 여부에서의 차이

프로세스 스케쥴러는 선점형 : 다른 프로세스의 실행권을 역으로 가져오는게 가능함.

Eventloop는 비선점형 : 실행중인 코루틴에서 await 하거나 return(종료) 해야지 Eventloop가 실행권을 가져오게 되고, 다른 코루틴에게 실행권을 넘겨줄 수 있다.

 

7. 

그래서 흔히들 편하게 쓰는게 코루틴함수 정의 해놓고 await gather( 코루틴함수() 1...2..3..4..) 로 while루프 돌린 다음 코루틴 함수마다 await asyncio.sleep(x) 함수를 사용해서 Eventloop 한테 계속해서 실행권을 떤져 주고 알아서 동시성있게 돌아가게끔 쓰레드 처럼 쓰는 경우가 많은듯....

 

 

8. Eventloop 와 MultiThread

하나의 쓰레드에는 하나의 Eventloop 만 존재한다.

Event loop를 여러개 돌리고 싶다면 single-Thread -> Multi-Thread 로 Event loop 를 여러개 돌리는 방법이 있다.

그래서 run_coroutine_threadsafe를 사용하면 코루틴을 어떤 스레드에서 실행할지 선택할 수도 있고, run_in_executor를 사용하면 Eventloop을 하나 더 만들지 않더라도 다른 스레드에서 함수를 실행할 수 있습니다. 

asyncio.run_coroutine_threadsafe( 코루틴객체, loop객체 )

awaitable loop.run_in_executor( executor: Executor , func: Callable , *args ) // executor가 None이면 기본 executor가 사용됨

Executor란 - 쓰레드풀 이용할때 쓰이는 거, 비동기적으로 호출을 실행하는 메서드를 제공하는 추상클래스.

 

 

9.

2. asyncio.Task 객체 asyncio.future객체 관계

asyncio.create_task()가 반환하는 것은 Task 객체이고, 이 객체는 Future를 상속받기 때문에 동일하게 Awaitable합니다.

2의 내용때문에 Task객체가 Future 객체의 기능을 똑같이 사용 할 수 있다.

저수준의 기능을 사용하기 위해서 Future 객체의 기능을 씀.

 

 

10.  Future 기능

Awaitable이기 때문에 await 키워드로 결과를 기다릴 수 있음
done()으로 연산이 끝났는지 확인 가능
set_result()로 연산의 결과를 지정
cancel()로 연산을 취소 가능
add_done_callback()으로 콜백함수 등록 가능

 

 

11. add_done_callback( ) 

add_done_callback( ) 함수로 callback 을 target 할 수 있는 건 일반함수만 가능하다.

코루틴 함수를 콜백으로 등록 하고 싶다면 일반 함수에서 코루틴함수를 Eventloop 에 등록하는 방법으로만 가능하다.

ex ) Future.add_done_callback(lambda fut: asyncio.create_task(some_async_func(fut)))

 

 

12. graceful shutdown

대충 이런 구조로 쓰자

 

def exit_event_loop(self, *args):
    self._event_loop_stopper.set()

    # 여기에다가 종료 관련 함수 사용

_event_loop = asyncio.get_event_loop()
print("loop : ", _event_loop)
print("loop type : ", type(_event_loop))
_event_loop.add_signal_handler(signal.SIGINT, self.exit_event_loop)
_event_loop.add_signal_handler(signal.SIGTERM, self.exit_event_loop)

 

 

 

 

댓글