41.1 코루틴에 값 보내기

Unit 41. 코루틴 사용하기

지금까지 함수를 호출한 뒤 함수가 끝나면 현재 코드로 다시 돌아왔습니다. 예를 들어서 다음과 같이 calc 함수 안에서 add 함수를 호출했을 때 add 함수가 끝나면 다시 calc 함수로 돌아옵니다. 특히 add 함수가 끝나면 이 함수에 들어있던 변수와 계산식은 모두 사라집니다.

def add(a, b):
    c = a + b    # add 함수가 끝나면 변수와 계산식은 사라짐
    print(c)
    print('add 함수')
 
def calc():
    add(1, 2)    # add 함수가 끝나면 다시 calc 함수로 돌아옴
    print('calc 함수')
 
calc()

이 소스 코드에서 calc 함수와 add 함수의 관계를 살펴보겠습니다. calc가 메인 루틴(main routine)이면 addcalc의 서브 루틴(sub routine)입니다. 이 메인 루틴과 서브 루틴의 동작 과정을 그림으로 나타내면 다음과 같은 모양이 됩니다.

그림 41-1 메인 루틴과 서브 루틴의 동작 과정

메인 루틴에서 서브 루틴을 호출하면 서브 루틴의 코드를 실행한 뒤 다시 메인 루틴으로 돌아옵니다. 특히 서브 루틴이 끝나면 서브 루틴의 내용은 모두 사라집니다. 즉, 서브 루틴은 메인 루틴에 종속된 관계입니다.

하지만 코루틴은 방식이 조금 다릅니다. 코루틴(coroutine)은 cooperative routine를 의미하는데 서로 협력하는 루틴이라는 뜻입니다. 즉, 메인 루틴과 서브 루틴처럼 종속된 관계가 아니라 서로 대등한 관계이며 특정 시점에 상대방의 코드를 실행합니다.

그림 41-2 코루틴의 동작 과정

이처럼 코루틴은 함수가 종료되지 않은 상태에서 메인 루틴의 코드를 실행한 뒤 다시 돌아와서 코루틴의 코드를 실행합니다. 따라서 코루틴이 종료되지 않았으므로 코루틴의 내용도 계속 유지됩니다.

일반 함수를 호출하면 코드를 한 번만 실행할 수 있지만, 코루틴은 코드를 여러 번 실행할 수 있습니다. 참고로 함수의 코드를 실행하는 지점을 진입점(entry point)이라고 하는데, 코루틴은 진입점이 여러 개인 함수입니다.

이번 유닛에서 설명할 코루틴은 초보자가 이해하기 어려운 내용입니다. 코루틴을 모르더라도 파이썬으로 프로그래밍하는데는 큰 지장이 없습니다. 이 부분이 어렵게 느껴진다면 그냥 건너뛰어도 됩니다. 나중에 프로그래밍에 익숙해졌을 때 다시 학습하는 것을 권장합니다.

41.1 코루틴에 값 보내기

코루틴은 제너레이터의 특별한 형태입니다. 제너레이터는 yield로 값을 발생시켰지만 코루틴은 yield로 값을 받아올 수 있습니다. 다음과 같이 코루틴에 값을 보내면서 코드를 실행할 때는 send 메서드를 사용합니다. 그리고 send 메서드가 보낸 값을 받아오려면 (yield) 형식으로 yield를 괄호로 묶어준 뒤 변수에 저장합니다.

  • 코루틴객체.send(값)
  • 변수 = (yield)

그럼 코루틴에 숫자 1, 2, 3을 보내서 출력해보겠습니다.

coroutine_consumer.py

def number_coroutine():
    while True:        # 코루틴을 계속 유지하기 위해 무한 루프 사용
        x = (yield)    # 코루틴 바깥에서 값을 받아옴, yield를 괄호로 묶어야 함
        print(x)
 
co = number_coroutine()
next(co)      # 코루틴 안의 yield까지 코드 실행(최초 실행)
 
co.send(1)    # 코루틴에 숫자 1을 보냄
co.send(2)    # 코루틴에 숫자 2을 보냄
co.send(3)    # 코루틴에 숫자 3을 보냄

실행 결과

1
2
3

먼저 코루틴 number_coroutinewhile True:로 무한히 반복하도록 만듭니다. 왜냐하면 코루틴을 종료하지 않고 계속 유지시키기 위해 무한 루프를 사용합니다(코루틴을 종료하는 방법은 뒤에서 설명하겠습니다). 그리고 x = (yield)와 같이 코루틴 바깥에서 보낸 값을 받아서 x에 저장하고, printx의 값을 출력합니다.

def number_coroutine():
    while True:        # 코루틴을 계속 유지하기 위해 무한 루프 사용
        x = (yield)    # 코루틴 바깥에서 값을 받아옴, yield를 괄호로 묶어야 함
        print(x)

코루틴 바깥에서는 co = number_coroutine()과 같이 코루틴 객체를 생성한 뒤 next(co)로 코루틴 안의 코드를 최초로 실행하여 yield까지 코드를 실행합니다(co.__next__()를 호출해도 상관없습니다).

  • next(코루틴객체)
co = number_coroutine()
next(co)      # 코루틴 안의 yield까지 코드 실행(최초 실행)

그다음에 co.send로 숫자 1, 2, 3을 보내면 코루틴 안에서 숫자를 받은 뒤 print로 출력합니다.

co.send(1)    # 코루틴에 숫자 1을 보냄
co.send(2)    # 코루틴에 숫자 2을 보냄
co.send(3)    # 코루틴에 숫자 3을 보냄

이 코루틴의 동작 과정을 그림으로 살펴보겠습니다.

먼저 next(co)로 코루틴의 코드를 최초로 실행하면 x = (yield)yield에서 대기하고 다시 메인 루틴으로 돌아옵니다.

그림 41-3 코루틴의 코드를 최초로 실행한 뒤 메인 루틴으로 돌아옴

그다음에 메인 루틴에서 co.send(1)로 1을 보내면 코루틴은 대기 상태에서 풀리고 x = (yield)x = 부분이 실행된 뒤 print(x)로 숫자를 출력합니다. 이 코루틴은 while True:로 반복하는 구조이므로 다시 x = (yield)yield에서 대기합니다. 그러고 나서 메인 루틴으로 돌아옵니다. 이런 과정으로 send가 보낸 값을 (yield)가 받게 됩니다.

그림 41-4 send로 값을 보내면 코루틴에서 값을 받아서 출력

계속 같은 과정으로 send를 사용하여 값을 보내면 코루틴에서 값을 받아서 출력합니다.

정리하자면 next 함수(__next__ 메서드)로 코루틴의 코드를 최초로 실행하고, send 메서드로 코루틴에 값을 보내면서 대기하고 있던 코루틴의 코드를 다시 실행합니다.

내용이 조금 어렵죠? 코루틴은 yield에서 함수 중간에 대기한 다음에 메인 루틴을 실행하다가 다시 코루틴을 실행한다는 점만 기억하면 됩니다.

참고 | send로 코루틴의 코드를 최초로 실행하기

지금까지 코루틴의 코드를 최초로 실행할 때 next 함수(__next__ 메서드)를 사용했지만, 코루틴객체.send(None)과 같이 send 메서드에 None을 지정해도 코루틴의 코드를 최초로 실행할 수 있습니다.