코루틴과 이벤트루프

Created
Jan 8, 2021
Created by
Tags
Python
Property
 
 
 

코루틴 (coroutine)

서브루틴은 caller가 callee를 호출하면 callee가 무조건 처음부터 끝까지 실행되고 return 한다.
반면에 코루틴은 callee가 return을 하는 것 뿐만 아니라 중단(suspend)했다가 중단된 곳에서 재개(resume) 하는 형태가 가능하다. 파이썬에서 대표적인 코루틴으로 제너레이터(generator)가 있다.
 
def generate(): print('start coroutine!') x = yield print('resume coroutine!: ', x) co = generate() print('co: ', co, end='\n\n') next(co) print('suspend coroutine!', end='\n\n') co.send(42)
co: <generator object generate at 0x10f5859e0> start coroutine! suspend coroutine! resume coroutine!: 42 Traceback (most recent call last): File "generator.py", line 13, in <module> co.send(42) StopIteration
 
코루틴은 4가지 상태를 가진다.
  • GEN_CREATED : 실행을 시작하기 위해 대기중인 상태
  • GEN_RUNNING : 인터프리터가 현재 코루틴을 실행하는 상태
  • GEN_SUSPENDED : yield문에서 대기중인 상태
  • GEN_CLOSED : 실행이 끝난 상태
 
co = generate()
제너레이터 객체가 만들어지고 아직 코루틴이 실행되고 있지 않다. (GEN_CREATED)
 
next(co)
next()는 코루틴을 시작하여 start coroutine!을 출력한다. (GEN_RUNNING)
 
print('start coroutine!') x = yield
yield 문에서 코루틴은 suspend 되고(GEN_SUSPENDED) 코루틴을 실행했던 루틴으로 돌아와 suspend coroutine!을 출력한다.
 
co.send(42) print('resume coroutine!: ', x)
 
yield 문의 값을 42로 만들어 코루틴을 resume한다. (GEN_RUNNING)
resume coroutine!을 출력하고 코루틴의 끝에 도달했으므로 StopIteration 예외를 일으킨다.
 
 
 

이벤트 루프 (event loop)

이벤트루프는 단일쓰레드 위에서 동작하는 구조체로, 비동기 태스크를 큐에서 꺼내 실행한다. (이벤트 루프, 메세지 디스패쳐 등은 프로그램의 이벤트나 메세지를 대기하다가 dispatch하는 구조체에 해당한다.)
 
  • 이벤트루프가 단일쓰레드에서 큐를 감시하고 있다.
  • 큐에 태스크가 들어오면 태스크를 꺼낸다. task1이라고 하자.
  • 큐에서 꺼낸 task1coroutine1을 실행한다.
  • coroutine1이 또 다른 coroutine2await coroutine2() 와 같이 부르면 coroutine1은 suspend 하고 context switch가 발생한다. 변수나 상태와 같은 coroutine1의 컨텍스트를 레지스터에 저장하고 나면 coroutine2의 컨텍스트를 load한다.
  • 만약에 코루틴에 I/O, sleep 이 있으면 코루틴은 suspend 하고 제어는 이벤트루프에 넘어간다.
  • 제어를 넘겨받은 이벤트루프는 다음 태스크 task2를 큐에서 꺼낸다.
  • 위의 과정이 task2에서도 반복된다.
notion image
한 가지 주의할 점은 코루틴을 await 할 때마다 context switch가 발생하므로 로직을 여러 코루틴으로 잘게 쪼개는 것이 항상 바람직하지는 않다는 것이다. 하지만 그렇다고 해서 IO-bound 한 환경에서 asyncio 대신에 sync & blocking 을 선택할 만큼 큰 단점으로 작용하는 것은 아니다. (https://stackoverflow.com/questions/54860285/how-can-i-prevent-context-switching-when-calling-an-async-function 참고)
 
 
 
 
출처
  • fluent python