[Advanced Python] -4

kimkrh·2022년 2월 8일
0

Python

목록 보기
9/14

Coroutine

  • 코루틴 : 루틴의 일종으로서, 협동 루틴이라 할 수 있다.
    루틴과 서브 루틴은 서로 비대칭적인 관계이지만, 코루틴들은 완전히 대칭적인, 즉 서로가 서로를 호출하는 관계이다.
    -> 어떠한 코루틴이 발동될 때마다 해당 코루틴은 이전에 자신의 실행이 마지막으로 중단되었던 지점 다음의 장소에서 실행을 재개한다.

  • 일반 함수를 호출하면 코드를 한 번만 실행할 수 있지만, 코루틴은 코드를 여러 번 실행할 수 있습니다.
  • 함수의 코드를 실행하는 지점을 진입점(entry point)이라고 하는데, 코루틴은 진입점이 여러 개인 함수입니다.
  • 코루틴 바깥에서 값을 받아오면서 값을 다시 바깥으로 전달하는 방법
    • 변수 = (yield 변수)
    • 변수 = next(코루틴객체) -> 코드는 실행하지만 값은 보내지 않는다.
    • 변수 = 코루틴객체.send() -> 코드를 실행하고 값을 보낸다.

      generator : next 함수를 반복 호출하여 값을 얻어낸다.
      coroutine : next 함수를 한번만 호출한 뒤 send로 값을 주고 받는 방식이다.

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

# 1
# 2
# 3

  • 코루틴을 종료하는 방법
    • 코루틴객체.close()
def number_coroutine():
    while True:
        x = (yield)
        print(x, end=' ')
 
co = number_coroutine()
next(co)
 
for i in range(20):
    co.send(i)
 
co.close()    # 코루틴 종료

# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
  • 코루틴 객체에서 close메소드를 호출하면 코루틴이 종료될 때 GeneratorExit 예외가 발생한다. -> 이 예외를 처리하면 코루틴의 종료 시점을 알 수 있다.
def number_coroutine():
    try:
        while True:
            x = (yield)
            print(x, end=' ')
    except GeneratorExit:    # 코루틴이 종료 될 때 GeneratorExit 예외 발생
        print()
        print('코루틴 종료')
 
co = number_coroutine()
next(co)
 
for i in range(20):
    co.send(i)
 
co.close()

# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
# 코루틴 종료
  • 코루틴 안에 예외를 발생시키는 throw메소드를 사용하여 코루틴을 종료시킬 수 있다.
    • 코루틴객체.throw(예외이름, 에러메시지)
def sum_coroutine():
    try:
        total = 0
        while True:
            x = (yield)
            total += x
    except RuntimeError as e:
        print(e)
        yield total    # 코루틴 바깥으로 값 전달
 
co = sum_coroutine()
next(co)
 
for i in range(20):
    co.send(i)
 
print(co.throw(RuntimeError, '예외로 코루틴 끝내기')) 
# 예외가 발생하면 에러 메시지를 출력하고
# 코루틴의 except에서 yield로 전달받은 누적된 값을 출력

# 예외로 코루틴 끝내기
# 190

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

def coroutine_test():
    greeting = "good"
    while True:
        text = (yield greeting)
        print("text= ", end= ""), print(text)
        greeting += text

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

    next(cr) # 코루틴 최초 실행 'good' 저장
    time.sleep(2)

    print("send 1")
    print(cr.send("morning")) # 마지막으로 저장된 'good' 뒤에서 실행을 재개
    time.sleep(2)

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

    print("send 3")
    print(cr.send("evening"))
    time.sleep(2)
    
# cr=<generator object coroutine_test at 0x000001F301A0CF90>
# send 1
# text= morning
# goodmorning
# send 2
# text= afternoon
# goodmorningafternoon
# send 3
# text= evening
# goodmorningafternoonevening
# 해결방법1
def coroutine_test():
    greeting = "good"
    while True:
        text = (yield greeting)
        print(greeting + text)

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

    next(cr)
    time.sleep(2)

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

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

    print("send 3")
    cr.send("evening")
    time.sleep(2)
    
# cr=<generator object coroutine_test at 0x0000026EAF53CF90>
# send 1
# goodmorning
# send 2
# goodafternoon
# send 3
# goodevening
# 해결방법2
def coroutine_test():
    greeting = "good"
    while True:
        text = (yield greeting)
        print("text = ", end=""), print(text)
        greeting = "good" # greeting 초기화
        greeting += 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)
    
# cr=<generator object coroutine_test at 0x000001743551CF90>
# send 1
# text = morning
# goodmorning
# send 2
# text = afternoon
# goodafternoon
# send 3
# text = evening
# goodevening

  1. 두번째 코드를 coroutine 과 asyncio 를 활용하여 구현해 보세요. asyncio에는 비동기 처리를 위한 event loop가 존재하고 여기에 코루틴을 등록해서 사용하는 구조로 되어있습니다
# 코루틴 2개로 일억을 증가시키는 예제
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")
    
# ret_value =100000000
# end of main
  • asyncio(Asynchronous I/O)
    • 비동기 프로그래밍을 위한 모듈이며 CPU 작업과 I/O를 병렬로 처리하게 해줍니다.
    • 고수준으로 구조화된 네트워크 구조에 적합하다.
    • 코루틴들을 동시에 실행하고 실행을 제어한다.
    • 동기 처리 : 특정 작업이 끝나면 다음 작업을 처리하는 순차처리 방식
    • 비동기 처리 : 여러 작업을 처리하도록 예약한 뒤 작업이 끝나면 결과를 받는 방식

import asyncio

async def coroutine_1(num): # 네이티브 코루틴 생성
    result_value = 0
    while result_value < num:
        result_value += 1
    return result_value

async def coroutine_2(num):
    result_value = 0
    while result_value < num:
        result_value += 1
    return result_value

async def main():
	# await : 해당 객체가 끝날 때까지 기다린 뒤 결과 반환
    one = await coroutine_1(50000000)
    two = await coroutine_2(50000000)
    # await asyncio.sleep(1.0) -> 1초 대기, asyncio.sleep도 네이티브 코루틴
    print("ret_value=", end="")
    print(one+two)
    print("end of main")

if __name__ == "__main__":
    loop = asyncio.get_event_loop() # 이벤트 루프를 얻음
    loop.run_until_complete(main()) # main이 끝날 때까지 기다림
    loop.close() # 이벤트 루프를 닫음
    
# ret_value=100000000
# end of main

0개의 댓글