Coroutine 의 개념
- yield 키워드 : 메인 루틴과 서브 루틴의 연결 기능을 해준다. 멈춰있다가 다음 루틴을 제어
- 코루틴의 제어, 코루틴 상태, 양방향 값 전송을 알아본다.
- yield from 코루틴
서브 루틴 : 메인 루틴에서 리턴에 의해 호출 부분으로 돌아와 다시 프로세스
쓰레드 : 지금까지 해온것은 싱글 쓰레드 작업, 멀티 쓰레드는 공유 자원의 교착 상태를 관리해야 하기 때문에 복잡하다. 컨텍스트 스위칭 비용 발생과 자원 소비 비용 고려
코루틴
루틴 실행 중 멈춤 가능, 특정 위치로 돌아갔다가 다시 원래 위치로 돌아와 수행
비동기 처리 방식으로 동시성 프로그래밍을 구현할 수 있다. 코루틴은 제너레이터를 기반으로 작동함
코루틴은 스케쥴링 오버헤드가 매우 적다. (= 컨텍스트 스위칭 비용이 적다)
항상 처음은 코루틴 next() 한번을 보내 다음 yield 까지 실행한다. 바로 send 할 수 없다.
send(x) x값이 yield 에 들어간다.
예를들어 n = yield k
이면 send(10) 을 보내면 이전에 k 는 none yield 에 10 따라서 n = 10 다음 send에 보임
def coroutine1():
print('>>> coroutine started.')
i = yield
print('>>> coroutine received : {}'.format(i))
# 제너레이터 선언
c1 = coroutine()
print(c1, type(c1))
# <generator object coroutine1 at 0x000001FB0BCDDCC8> <class 'generator'>
# yield 실행 전까지 진행
next(c1)
# >>> coroutine started.
# 기본으로 None 전달
next(c1)
# >>> coroutine received : None
# 값 전송
# c1.send(100)
# 잘못된 사용
c2 = coroutine1()
# c2.send(100) # 예외 발생, 코루틴은 next() 로 시작해야 함
코루틴 내장 함수
GEN_CREATED
: 처음 대기 상태
GEN_RUNNING
: 실행 상태
GEN_SUSPENDED
: yield 대기 상태
GEN_CLOSED
: 실행 완료 상태
def coroutine2(x):
print('>>> coroutine started : {}'.format(x))
y = yield x
print('>>> coroutine received : {}'.format(y))
z = yield x + y
print('>>> coroutine received : {}'.format(z))
c3 = coroutine2(10)
from inspect import getgeneratorstate
print(getgeneratorstate(c3))
# GEN_CREATED
print(next(c3))
# >>> coroutine started : 10
# 10
print(getgeneratorstate(c3))
# GEN_SUSPENDED
print(c3.send(15))
# >>> coroutine received : 15
# 25
print(c3.send(20)) # 다음 yield가 없어서 예외 발생 StopIteration
# >>> coroutine received2 : 20
"""
Traceback (most recent call last):
# print(c3.send(20))
StopIteration
"""
데코레이터 패턴을 사용하는 코루틴
from functools import wraps
def coroutine(func):
'''Decorator run until yield'''
@wraps(func)
def primer(*args, **kwargs):
gen = func(*args, **kwargs)
next(gen) # 데코레이터를 사용해서 첫 next 를 항상 해주게 함
return gen
return primer
@coroutine
def sumer():
total = 0
term = 0
while True:
term = yield total
total += term
su = sumer()
print(su.send(100))
print(su.send(40))
print(su.send(60))
예외 처리를 통한 코루틴
class SampleException(Exception):
'''설명에 사용할 예외 유형'''
def coroutine_except():
print('>> coroutine stated.')
try:
while True:
try:
x = yield
except SampleException:
print('-> SampleException handled. Continuing..')
else:
print('-> coroutine received : {}'.format(x))
finally:
print('-> coroutine ending')
exe_co = coroutine_except()
print(next(exe_co))
print(exe_co.send(10))
print(exe_co.send(100))
print(exe_co.throw(SampleException)) # 강제로 예외 발생시켜도 코루틴이 안닫힘
print(exe_co.send(1000))
print(exe_co.close()) # GEN_CLOSED
리턴값이 있는 코루틴
# 평균 값을 계속 리턴해주는 코루틴
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)
avger2 =averager_re()
next(avger2)
avger2.send(10)
avger2.send(30)
avger2.send(50)
try:
avger2.send(None)
except StopIteration as e:
print(e.value)
# 예외처리의 value 값 문자열 리턴을 확인 가능
중첩 코루틴 처리
yeild from 이 자동으로 StopIteration 을 처리해준다. 3.7 이후로 await 으로 명칭 변경
def gen1():
for x in 'AB':
yield x
for y in range(1,4):
yield y
t1 = gen1()
print(next(t1))
# A
print(next(t1))
# B
print(next(t1))
# 1
print(next(t1))
# 2
print(next(t1))
# 3
# print(next(t1))
# 에러
t2 = gen1()
print(list(t2))
# ['A', 'B', 1, 2, 3]
def gen2():
# gen1 보다 빠르게 순차적 처리를 잘해줌
yield from 'AB'
yield from range(1,4)
t3 = gen2()
print(next(t3))
# A
print(next(t3))
# B
print(next(t3))
# 1
print(next(t3))
# 2
print(next(t3))
# 3
# print(next(t3))
# 에러
t4 = gen2()
print(list(t4))
# ['A', 'B', 1, 2, 3]
def gen3_sub():
print('Sub coroutine.')
x = yield 10
print('Recv : ', str(x))
x = yield 100
print('Recv : ', str(x))
def gen4_main():
yield from gen3_sub()
t5 = gen4_main()
print(next(t5))
# Sub coroutine.
# 10
print(t5.send(7))
# Recv : 7
# 100
print(t5.send(77))
# Recv : 77
# 예외 발생
# StopIteration