Coroutine

Junyoung Kim·2022년 1월 6일
0

Python+

목록 보기
7/7

어떠한 함수 안에 또 다른 함수가 있을 때, 첫번째 함수와 함수 안에 들어있는 함수를 각각
메인 루틴과 서브 루틴이라고 부른다. 예를 들어 다음과 같은 함수를 보자.

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

calc함수에서 add함수를 호출했을 때, add함수가 끝나면 다시 calc함수로 돌아오게 된다. 이 관계를 메인 루틴, 서브 루틴이라고 한다면 calc 함수는 메인 루틴, add 함수는 서브 루틴이라고 할 수 있다. 메인루틴과 서브루틴의 호출 과정을 그림으로 보면 다음과 같다.


메인 루틴에서 서브 루틴을 호출하면 서브 루틴의 코드를 실행한 뒤 다시 메인 루틴으로 돌아온다. 이 과정에서 서브 루틴이 끝나면 서브 루틴의 내용은 모두 사라진다. 즉, 서브 루틴은 메인 루틴에 종속된 관계라고 할 수 있다.

코루틴coroutine은 서브 루틴과 달리 메인 루틴과 협력한다는 루틴이라는 뜻이다. 즉, 서로 종속된 관계가 아니라 대등한 관계라고 할 수 있다. 코루틴은 여러번 호출될 수 있고 호출되면서 상태를 유지할 수 있다. 함수에서 코드를 실행하는 지점을 진입점(entry point)라고 하는데, 코루틴은 진입점이 여러 개인 함수이다. 코루틴의 호출 과정을 그림으로 보면 다음과 같다.

코루틴은 앞에서 살펴보았던 Generators와 유사한데, 다음 코드를 살펴보자.

import time

def coroutine_test():
    greeting = "good "
    while True:
        text = (yield greeting) # coroutine instance
        print("text = ",end=""), print(text)
        greeting += text 
        # main 함수에서 send() 메서드 실행 시 "morning", "afternoon", "evening"을 text에 저장

if __name__ == "__main__":
    cr = coroutine_test()
    print("cr=",end=""), print(cr)

    next(cr)
		time.sleep(2)

    print("send 1")
    print(cr.send("morning"))
    time.sleep(2)

    print("send 2")
    print(cr.send("afternoon"))
    time.sleep(2)

    print("send 3") 
    print(cr.send("evening"))
    time.sleep(2)


코루틴은 제너레이터와 비슷하게 yield를 사용하나, 제너레이터는 yield로 값을 산출하였지만 코루틴은 값을 받아온다는 차이점이 있다. 위 코드처럼 파이썬에서 코루틴은 yield와 소괄호로 나타낼 수 있다.

주석에서 main 함수에서 send() 메서드 실행 시 "morning", "afternoon", "evening"을 text에 저장한다고 나왔는데, 코루틴은 여러번 호출 될 때 현재 상태를 유지하는 특징이 있어서 출력 때마다 "morning", "afternoon", "evening"이 붙여서 나오는 출력결과가 나왔다.


스레드, 프로세스에서 다뤄보았던 1억을 증가시키는 코드의 코루틴 버전으로 살펴보자.

def coroutine_1():
    return_value = 0
    while True:
        input_value = (yield return_value)
        return_value = input_value + 1

def coroutine_2():
    return_value = 0
    while True:
        input_value = (yield return_value)
        return_value = input_value + 1

if __name__ == "__main__":
    ret_value = 0

    c1 = coroutine_1()
    c2 = coroutine_2()

    next(c1)
    next(c2)

    while ret_value < 100000000:
        
        ret_value = c1.send(ret_value)

        ret_value = c2.send(ret_value)

    print("ret_value =",end=""), print(ret_value)
    print("end of main")

코루틴은 send() 메서드를 호출하기 전에는 코루틴이 수행되지 않고 while문의 yield에서 대기한다. 따라서 내가 어떤 로직을 수행하다가 원하는 시점에 send() 메서드를 호출해서 코루틴과 협력해서 작업을 할 수 있고, 또한 c1과 c2의 코루틴에 보내는 중간에 어떤 원하는 작업을 할 수 있다.
코루틴은 이처럼 앞서 살펴보았던 멀티태스킹 기법과 비교해서 자원의 동기화 문제에서 자유롭다는 장점이 있어서 협력형 멀티태스킹 활용에 유리하다.

asyncio(Asynchronous I/O)는 코루틴과 관련된 async/await 구문을 사용하여 동시성 코드를 작성하는 라이브러리이며, 여러가지 파이썬의 비동기처리 프레임워크의 기반으로 사용된다. 이 라이브러리를 사용하면 CPU 작업과 I/O를 병렬로 처리하게 해준다. 앞서 살펴본 코루틴은 제너레이터 기반으로 작성되었는데, 제너레이터 기반 코루틴은 파이썬 3.10버전에서 삭제될 예정이므로 파이썬에서 코루틴을 활용하기 위해서는 asyncio를 적극적으로 활용할 필요가 있다.

Assignment

  1. 첫번째 예제 코드는 greeting에 문자열이 send 보낼때 마다 계속 더해지는 문제가 있습니다.
    다음과 같이 send 호출시마다 good morning, good afternoon, good evening 이 출력되도록 코드를 수정해보세요
    print(cr.send("morning")) → good morning
    print(cr.send("afternoon")) → good afternoon
    print(cr.send("evening")) → good evening
import time

def coroutine_test():
    greeting = "good "
    while True:
        text = (yield greeting)
        print("text = ",end=""), print(text)
        greeting = "good " 
        # greeting의 초기화를 위해 무한히 반복하는 while문에 "good " string 추가
        greeting += text

if __name__ == "__main__":
    cr = coroutine_reset()
    print("cr=",end=""), print(cr)

    next(cr)
    time.sleep(2)

    print("send 1")
    print(cr.send("morning"))
    time.sleep(2)

    print("send 2")
    print(cr.send("afternoon"))
    time.sleep(2)

    print("send 3") 
    print(cr.send("evening"))
    time.sleep(2)
  1. 아래 공식문서에서 asyncio 에 대해서 학습한 후 두번째 코드를 coroutine 과 asyncio 를 활용하여 구현해 보세요.
import asyncio
import time

async def coroutine_1(): #async문으로 네이티브 코루틴 coroutine_1 정의
    return_value = 0
    while return_value < 50000000:
        return_value += 1
    return return_value

async def coroutine_2(): # coroutine_2 정의
    return_value = 0
    while return_value < 50000000:
        return_value += 1
    return return_value

async def main(): #최상위 진입점 main() 정의
    co1= await coroutine_1() # await문으로 해당 객체가 끝날 때 까지 기다린 뒤 결과 반환
    co2= await coroutine_2() 
    print("ret_value =",end=""), print(co1+co2) #코루틴의 결과값 더하기
    print("end of main")

if __name__ == "__main__":
    start_time = time.time() 
    
    asyncio.run(main()) #asyncio 프로그램 실행 후 결과 반환
    
    print(f"--- {time.time() - start_time} seconds ---") # 처리 시간 계산    

0개의 댓글