본문 바로가기

Python/Intermediate

[Python] 병행성(Concurrency)(3) - Coroutine

  • 흐름제어
    병행성(Concurrency) : 한 컴퓨터가 여러 일을 동시에 수행 -> 단일 프로그램안에서 여러일을 쉽게 해결
    병렬성(Parallelism) : 여러 컴퓨터가 여러 작업을 동시에 수행 -> 속도
    코루틴(Coroutine) : 단일(싱글) 스레드, 스택을 기반으로 동작하는 비동기 작업
    스레드 : OS 관리, cpu 코어에서 실시간, 시분할 비동기 작업 -> 멀티스레드
    yield, send : 메인 <==> 서브
    코루틴 제어, 상태, 양방향 전송
    yield from
    서브루틴 : 메인루틴에서 호출 -> 서브루틴에서 수행(흐름제어)
    코루틴 : 루틴 실행 중 중지 -> 동시성 프로그래밍
    코루틴 : 스레드에 비해 오버헤드 감소
    스레드 : 싱글스레드 -> 멀티스레드 -> 복잡 -> 공유되는 자원 -> 교착 상태 발생 가능성, 컨텍스트 스위칭 비용 발생, 자원 소비 가능성 증가
    3.5이상에서, def -> async, yield -> await

 

# 코루틴 Ex1
def coroutine1():
    print('>>> coroutine started.')
    i = yield
    print('>>> coroutine received : {}'.format(i))

# 제네레이터 선언
cr1 = coroutine1()

print('제너레이터 선언:', cr1, '~', type(cr1))
# 제너레이터 선언: < generator object coroutine1 at 0x000002B8320447D8 > ~ < class 'generator' >

# 메인 루틴
# yield 지점 까지 서브루틴 수행
next(cr1) # "i = yield"까지 실행됨, 그래서 첫번째 print 출력됨
# >>> coroutine started.

# 기본 전달 값 None
# next(cr1) # yield 한개라서 예외발생함
# >>> coroutine received : None
# Traceback (most recent call last):
#     next(cr1)
# StopIteration


# 값 전송
print('--------------------')
cr1.send(100) # yield 에서 대기중일때만 send 가능.
# >>> coroutine received : 100
# Traceback (most recent call last):
#     cr1.send(100)
# StopIteration

 

 

잘못된 사용 예시

 

# 잘못된 사용

cr2 = coroutine1()

cr2.send(100) # 예외 발생, yield 지점이 아닌데, send 호출시 오류 발생.
# ----> 5 cr2.send(100) # 예외 발생
# TypeError: can't send non-None value to a just-started generator

 

 

코루틴 상태확인

 

# 코루틴 Ex2
# GEN_CREATED : 처음 대기 상태
# GEN_RUNNING : 실행 상태
# GEN_SUSPENDED : yield 대기 상태, 이때 send 가능. 제일 중요한 상태.
# 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))


cr3 = coroutine2(10)

from inspect import getgeneratorstate
print(getgeneratorstate(cr3))
# GEN_CREATED
print(next(cr3))
# >>> coroutine started : 10
# 10

print(getgeneratorstate(cr3))
# GEN_SUSPENDED
print(cr3.send(15))
# >>> coroutine received : 15
# 25

# print(cr3.send(20)) # 예외, 한번 더 호출하면 예외 발생해서 z 는 출력 안됨.
# # >>> coroutine received : 20
# # Traceback (most recent call last):
# #     print(cr3.send(20)) # 예외, 한번 더 호출하면 예외 발생해서 z 는 출력 안됨.
# # StopIteration

# 코루틴 Ex3
# StopIteration 자동 처리(3.5 -> await)
# 중첩 코루틴 처리
def generator1():
    for x in 'AB':
        yield x
    for y in range(1,4):
        yield y

t1 = generator1()

print(next(t1))
print(next(t1))
print(next(t1))
print(next(t1))
print(next(t1))
# A
# B
# 1
# 2
# 3

# print(next(t1)) # 예외 발생, exception
# # Traceback (most recent call last):
# #     print(next(t1))
# # StopIteration

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

 

 

yield from 

 

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


t3 = generator2()

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

# print(next(t3)), exception
# # Traceback (most recent call last):
# #     print(next(t3))
# # StopIteration

 

 

 

조금 더 - 함수 객체를 리턴할지, 아니면 제너레이터 객체를 리턴할지 명확히 해야함.

 

def make_generator():
    factor = 2

    def multiplier(n):
        # nonlocal factor
        return factor * n # 값 변경이 없어서 nonlocal factor 선언 필요없음.

    def gen():
        for i in range(5):
            yield multiplier(i)  # 클로저를 활용한 제너레이터

    return gen # 함수객체 반환
    # return gen() # gen()를 리턴하면 generator 객체 생성됨/객체를 반환

# return gen 인 경우
g = make_generator()
print(list(g()))  # [0, 2, 4, 6, 8]


# return gen() 인 경우
# g = make_generator()
# print(list(g))  # [0, 2, 4, 6, 8]