개발/Python

Coroutine

Louisus 2020. 7. 31. 22:09
728x90
# yield
# Coroutine

# yield: 메인 루틴 <-> 서브 루틴 간의 통신 가능하게 함
# 코루틴 제어, 코루틴 상태, 양방향 값 전송
# yield from

# 서브루틴: 메인루틴에서 -> 리턴에 의해 호출 부분으로 돌아와 다시 프로세스
# 코루틴: 루틴 실행 중 멈춤 가능 -> 특정 위치로 돌아갔다가 다시 원래 위치로 돌아와 수행 가능
# 코루틴: 스케줄링 오버헤드가 매우 적다. 하나의 스레드에서 실행하기 때문
# 스레드 : 싱글스레드 -> 멀티스레드: 복잡, 공유되는 자원에 대한 교착 상태 발생 가능성, 컨텍스트 스위칭 비용 발생, 자원 소비 가능성 증가

# 코루틴 예제1

# 여러 함수를 비동기 수행. 동시성 프로그래밍 가능
from functools import wraps
from inspect import getgeneratorstate


def coroutine1():
    print('>>> coroutine started')
    i = yield
    print('>>> coroutine received : {}'.format(i))


# 제너레이터 선언
c1 = coroutine1()
# <generator object coroutine1 at 0x7ff783575270> <class 'generator'>
print(c1, type(c1))

# yield 실행 전까지 실행
next(c1)
# 기본으로 None 전달
# next(c1)

# 값 전송
# c1.send(100)

# 잘못된 사용
c2 = coroutine1()
# TypeError: can't send non-None value to a just-started generator
# 먼저 next 실행해야 함
# c2.send(100)

# 코루틴 예제2
# GEN_CREATED: 처음 대기 상태
# GEN_RUNNING: 실행 상태
# GEN_SUSPENDED: yield 대기 상태
# GEN_CLOSED: 실행 완료 상태


def coroutine2(x):
    print('>>> coroutine started: {}'.format(x))
    # x 메인 루틴으로 부터 값을 전송 받음
    # y -> 메인루틴이 코루틴에게 send로 보내줘야 되는 값
    y = yield x
    print('>>> coroutine received: {}'.format(y))
    z = yield x+y
    print('>>> coroutine received: {}'.format(z))


c3 = coroutine2(10)

print(getgeneratorstate(c3))
print(next(c3))
# y 에서 대기 상태
print(getgeneratorstate(c3))
# z 에서 대기 상태
print(c3.send(15))

# >>> coroutine received: 20
# StopIteration
# print(c3.send(20))

# decorator로 코루틴 만들기

# 데코레이터 패턴

# 데코레이터를 통해 next 호출하는 부분을 편하게 수정


def coroutine(func):
    """Decorator run until yield"""
    # 코멘트, 내부 어트리뷰트 등을 모두 가지고 가겠다는 의미
    @wraps(func)
    def primer(*args, **kwargs):
        gen = func(*args, **kwargs)
        next(gen)
        return gen
    return primer

# send로 보내는 값을 계속 더함


@coroutine
def sumer():
    total = 0
    term = 0
    while True:
        term = yield total
        total += term


s = sumer()

print(s.send(100))
print(s.send(100))
print(s.send(100))


# 코루틴 예제3(예외 처리)

class SampleException(Exception):
    """설명에 사용할 예외 유형"""


def coroutine_except():
    print('>>> coroutine started')
    try:
        while True:
            try:
                x = yield
            except SampleException:
                print('-> SampleException handled. Continuing...')
            else:
                print('-> coroutine received: {}'.format(x))
    finally:
        print('-> coroutine ending')


# >>> coroutine started
# None
# -> coroutine received: 10
# None
# -> coroutine received: 10
# None
# -> SampleException handled. Continuing...
# None
# -> coroutine received: 1000
# None
# -> coroutine ending
# None

exe_co = coroutine_except()
print(next(exe_co))
print(exe_co.send(10))
print(exe_co.send(10))
print(exe_co.throw(SampleException))
print(exe_co.send(1000))
print(exe_co.close())  # GEN_CLOSED

# 코루틴 예제4


def averager_re():
    total = 0.0
    cnt = 0
    avg = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        cnt += 1
        avg = total / cnt
    return 'Average: {}'.format(avg)


avg2 = averager_re()
next(avg2)
avg2.send(10)
avg2.send(30)
avg2.send(40)

# Average: 26.666666666666668
try:
    avg2.send(None)
# 코루틴에서 리턴으로 반환한 값은 예외처리의 value 값으로 확인 가능
except StopIteration as e:
    print(e.value)

# 코루틴 예제5 (yield from)
# StopIteration 자동 처리) -> 3.7 version < -> await으로 바뀜
# 중첩 코루틴 처리


def gen1():
    for x in 'AB':
        yield x
    for y in range(1, 4):
        yield y


t1 = gen1()
print(next(t1))
print(next(t1))
print(next(t1))
print(next(t1))
print(next(t1))
# StopIteration
# print(next(t1))

t2 = gen1()
# ['A', 'B', 1, 2, 3]
print(list(t2))

# 중첩된 경우 더 빨리 사용 가능
# yield from 으로 치환 가능


def gen2():
    yield from 'AB'
    yield from range(1, 4)


# A
# B
# 1
# 2
# 3
t3 = gen2()
print(next(t3))
print(next(t3))
print(next(t3))
print(next(t3))
print(next(t3))

t4 = gen2()
# ['A', 'B', 1, 2, 3]
print(list(t4))


def gen3_sub():
    print('Sub coroutine.')
    x = yield 10
    print('Recv: ', str(x))
    x = yield 100
    print('Recv: ', str(x))

# yield from 으로 코루틴간의 통신을 흐름 제어 가능
# main routine에서 서브 루틴을 관리하는 함수를 만들고
# 여러 만들어진 코루틴을 통한 통신관계 사용 가능


def gen4_main():
    yield from gen3_sub()


t5 = gen4_main()

# Sub coroutine.
# 10
# Recv:  7
# 100
# Recv:  77
# StopIteration
print(next(t5))
print(t5.send(7))
print(t5.send(77))