궁금한게 많은 개발자 노트

[python] asyncio (1) 본문

Language

[python] asyncio (1)

궁금한게 많은 개발자 2022. 5. 23. 12:13

python 3.5부터 지원하는 asyncio는 비동기 프로그래밍을 위한 모듈입니다.

asyncio는 async/await 구문을 사용하여 동시성 코드를 작성하는 라이브러리입니다.

asyncio는 고성능 네트워크 및 웹 서버, 데이터베이스 연결 라이브러리, 분산 작업 큐 등을 제공하는 여러 파이썬 비동기 프레임워크의 기반으로 사용됩니다.

https://docs.python.org/ko/3.8/library/asyncio.html

 

asyncio — 비동기 I/O — Python 3.8.13 문서

 

docs.python.org

asyncio를 이해하기 위해서는 아래 키워드들을 순차적으로 이해를 해야 더 확실히 알 수 있을 것 같아 정리하려 합니다.

 

 

 

 

[ generator ]

제네레이터는 말그래도 값을 생성하는 함수입니다. 알다시피 함수는 값을 반환한 다음 자신의 스코프를 소멸시키고 , 다시 함수를 호출하면, 처음부터 다시 시작됩니다. 즉 한 번 실행됩니다. 그러나 제네레이터 함수는 값을 생성하고 함수의 실행을 일시 중지 할 수 있습니다. 컨트롤이 호출 스코프로 반환되며 ,원하는 경우 실행을 다시 시작하여 다른 값 (있는 경우)을 얻을 수 있습니다.

일반적인 함수를 실행하면 함수의 바디가 실행되지만, 제네레이터는 함수의 객체가 반환됩니다. 제네레이터 객체는 iterator와 동일하게 __next__()메소드를 가지고 있습니다.

아래와 같은 제네레이터 함수를 작성함으로써 메모리를 할당할 수 없는 크기의 배열을 출력하는 경우에도 제네레이터를 사용하면 가능하게 됩니다.

# 제네레이터 함수는 하나의 값을 반환하는 대신 실행을 일시 중지하고 여러 값을 생성 할 수있는 함수입니다. 또한 호출되면 iterable처럼 작동하는 제네레이터 객체를 제공하며 반복적으로 값을 얻을 수 있습니다.

def test():
    for i in range(10):
        yield i * 2

https://peps.python.org/pep-0255/

 

PEP 255 – Simple Generators | peps.python.org

PEP 255 – Simple Generators Author nas at arctrix.com (Neil Schemenauer), tim.peters at gmail.com (Tim Peters), magnus at hetland.org (Magnus Lie Hetland) Status Final Type Standards Track Requires 234 Created 18-May-2001 Python-Version 2.2 Post-History

peps.python.org

 

 

 

 

[ coroutine ]

coroutine은 한번에 하나의 코드에서만 동작하며, thread와 비교되어 설명되지만 완전히 다른 동작을 합니다.

thread의 경우에는 비동기식으로 여러 thread가 한번에 동작할 수 있지만 coroutine의 경우에는 yield키워드를 통해 호출자와 피호출자 함수사이에서 이동하며 실행되는 방식입니다.

 

coroutine은 generator와 yield를 사용하는 것에서는 동일하지만, send()메소드를 사용하여 값을 제네레이터로 보낼 수 있다는 점에서 차이점을 가집니다. 사용 방법으로는 처음에는 코루틴 객체를 받고, next를 호출하여 yield까지 진행을 하는 부분까지는 동일하다.

그 이후에는 send()메소드를 사용하여 coroutine에서 사용하는 변수에 값을 전달할 수 있습니다. 전달 후에는 마찬가지로 yield까지 또 진행이된 다음 값을 할당받고 yield에서 멈춥니다.

yield뒤에는 변수가 함께와서 사용되지만, yield만 사용될 때는 입력만 받는 용도로 사용됩니다.

 

coroutine을 실행하기 위한 caller와 callee(coroutine)간의 비동기 방식 프로그래밍의 방식으로 추가된 것은 다음과 같다.

1. coroutine에서 caller로 예외 발생 전달은 try-finally구문으로 처리 가능

2. caller에서 coroutine으로 예외 전달은 send와 같이 throw를 사용하여 처리 가능

3. caller에서 중단 되어 있는 coroutine을 종료 시키는 close메소드 사용 가능 (내부적으로 throw사용)

 

# yield from 키워드를 사용하여 subtask의 동작에서도 coroutine을 사용 가능. (coroutine내에서 coroutine을 호출)

 

 

 

 

[ asyncio ]

event loop방식의 비동기 프로그래밍 asyncio가 표준 라이브러리로 추가되었습니다.

event loop는 C언어에서 사용하는 것처럼 callback형식으로 사용이 가능하지만, coroutine과 함께 사용한다면 많은 이점

단일 thread에서 muiti-thread를 사용하는 것과 같은 효과

 

asyncio에서도 cocurrent.futures.Future과 유사한 asyncio.future를 제공한다. 차이점은 일반 함수가 아니라 coroutine을 전달하는 것이고, future.result()함수가 blocking되지 않는다는 점(단일 thread에서 event loop로 돌기 때문)

 

asyncio를 이해하는데 혼동되는 부분은 future때문. asyncio는 future없이도 callback만 사용이 가능하며 call_later() 등의 함수를 사용하여 callback을 등록하고 사용 가능합니다. 하지만 이것만 사용하는 경우에는 asyncio의 장점을 살릴 수 X

call_later()를 사용하여 callback을 등록하면 event loop에서 적절한 시점에 callback을 호출

 

coroutine은 future을 이용하여 사용합니다. 실제적으로 future를 직접 사용하지 않고, 이를 상속받은 Task Class를 사용

future와 task의 차이점은 future은 coroutine을 예외 처리를 위해 감싼 것이고, task는 여기에 event loop와 같이 연계

https://peps.python.org/pep-0318/

 

PEP 318 – Decorators for Functions and Methods | peps.python.org

Guido asked for a volunteer to implement his preferred syntax, and Mark Russell stepped up and posted a patch to SF. This new syntax was available in 2.4a2. @dec2 @dec1 def func(arg1, arg2, ...): pass This is equivalent to: def func(arg1, arg2, ...): pass

peps.python.org

@asyncio.coroutine을 살펴보면 함수 앞에 사용되면서 decorator역할을 합니다. decorator도 함수이며 함수를 parameter로 받아 다시 함수를 리턴합니다. 기존 함수를 변형하는 용도로 사용하며 함수의 입출력을 바꾸거나 trace를 하는 용도로 사용됩니다. 실제로 특별한 기능을 수행하기 보다는 asyncio와 같이 사용하는 coroutine이라고 표기하는 documentation목적입니다.

 

coroutine은 일반적으로 caller에서 반복적으로 next, send를 이용하여 yield에 멈춰있는 coroutine을 재개시킵니다. coroutine에서는 내부적으로 다른 coroutnie을 호출할 수 있으며 yield from으로 중첩도 가능합니다.

send()를 반복적으로 호출하는 것을 asyncio의 event loop에서 한다고 보면 됩니다. 이렇게 되면 coroutine도 event loop에서 마치 별도의 thread에서 도는 것과 같은 동작을 수행하게 됩니다. 이들 coroutine을 event loop에서 관리하기 위해서 future를 상속받은 Task Class를 사용하는것입니다.

 

일반 callback함수는 call_later()를 이용하여 event loop에 등록하고 coroutine은 ensure_future()나 loop.create_task()를 사용하여 등록할 수 있습니다. # 추가로 이전에 yield from에는 iterator, generator(coroutine)이 사용 가능 했는데, 여기에 future도 사용이 가능하도록 추가되었습니다.

아직, asnyc/await를 사용하지 않고 asyncio를 callback, coroutine으로 사용한 예제는 다음과 같습니다.

import asyncio

@asyncio.coroutine
def print_every_second_coroutine(type):
    "Print seconds"
    while True:
        for i in range(10):
            print(i, 's (corotine {})'.format(type))
            yield from asyncio.sleep(1)
        loop = asyncio.get_event_loop()
        loop.stop()

def print_every_seconds_callback(i):
    print (i, 's (callback)')
    loop = asyncio.get_event_loop()
    loop.call_later(1.0, print_every_seconds_callback, i+1)

def print_every_seconds_callback_to_coroutine():
    asyncio.ensure_future(print_every_second_coroutine('B'))

loop = asyncio.get_event_loop()
loop.call_soon(print_every_seconds_callback, 0)
loop.call_soon(print_every_seconds_callback_to_coroutine)
asyncio.ensure_future(print_every_second_coroutine('A'))

loop.run_forever()
loop.close()

print_every_second_coroutine()은 asyncio.ensure_future()를 이용하여 default event handler에 coroutine을 등록한다. 이때는 generator나 future를 등록하여야 하기 때문에 print_every_second_coroutine('A') 와 같이 generator를 리턴 받아서 등록한다. 이는 바로 event loop (loop.run_forever())에서 호출된다.


callback은 print_every_seconds_callback 와 같이 함수 이름을 전달한다. 만일 함수에 parameter 전달 조건이 맞지 않는다면 functiontools.partial을 이용 할 수 있다. 기본적으로 one shot 이기 때문에 callback 함수에서는 call_later() 등의 method를 이용하여 반복해서 호출해준다. (여러번 등록 필요)


print_every_seconds_callback_to_coroutine()과 같이 일반 callback 함수에서는 coroutine을 직접 호출할 수 없다(직접 호출하려면 이 함수가 next()를 반복해서 호출하여야 하기때문에 event loop가 blocking된다). 대신, coroutine을 등록 하는 것과 동일하게 asyncio.ensure_future()(또는 loop.create_task())를 사용한다.

 

 

 

'Language' 카테고리의 다른 글

[ python ] event loop (1)  (0) 2022.05.24
[ python ] asyncio (2)  (0) 2022.05.24
Node.js convention  (0) 2022.04.07
Node.js npm, yarn, package.json  (0) 2022.04.07
Node.js require과 module, resolution  (0) 2022.04.07
Comments