• Asyncio는 파이썬의 병행 프로그래밍 도구로 스레드나 멀티 프로세싱 대비 가벼운 편이다.

  • 구조에 대해서 간단히 설명하지면, 이벤트 루프를 통해서 태스크를 실행하는 방식이다.

  • 다른 방식들과 가장 큰 차이점은 각 태스크에서 이벤트 루프로 제어권을 다시 넘겨줄 시점을 지정한다는 것이다.

ASYNCIO 기능

  • asyncio 이벤트 루프 사용하기
  • async/await 함수 호출하기
  • 루프에서 실행할 태스크 작성하기
  • 여러 개의 태스크가 완료되길 기다리기
  • 모든 병행 태스크 종료 후 루프 종료하기
  • 전체 asyncio API 중 일부는 위와 같이 요약할 수 있다.
import asyncio
import time


async def main():
    print(f'{time.ctime()} Hello!')
    await asyncio.sleep(1.0)
    print(f'{time.ctime()} GoodBye!')


asyncio.run(main())
  • 위는 간단한 파이썬의 Asyncio를 사용한 예제이다.
  • 실행결과는 아래와 같다.

  • 눈치채지 어려울 수 도 있지만, Hello! 라는 문자열이 출력되고 나서 1초 후에 GoodBye! 라는 문자열이 출려된다.
import asyncio
import time


async def main():
    print(f'{time.ctime()} Hello!')
    await asyncio.sleep(1.0)
    print(f'{time.ctime()} GoodBye!')

loop = asyncio.get_event_loop() # 코루틴을 실행하기 위한 루프 인스턴스를 얻는 방법이다.
task = loop.create_task(main()) # create_task()를 호출해서 루프에 코루틴을 스케줄링 한다.
loop.run_until_complete(task) # 호출을 통해 현재 스레드를 블로킹 할 수 있다. 루프가 실행되는 동안 다른 작업들도 같이 실행된다. 
                              # asyncio.run() 도 내부에서 run_until_complete()를 호출하여 메인 스레드를 블로킹한다.
pending = asyncio.all_tasks(loop=loop)
for task in pending:
    task.cancel()
group = asyncio.gather(*pending, return_exceptions=True) # 루프 중지 증으로 블로킹 상태가 풀린 후에 아직 실행중인 태스크를 취합하고  
                                                         # 모든 태스크에게 취소 요청을 한 후에 loop.run_until_complete()를 호출하여 태스크들이 모두 종료 상태가 될 때까지 기다린다.
                                                         # asyncio.run()의 내부에서 위의 절차를 모두 포함한다.
loop.run_until_complete(group)
loop.close() # 보통 최종 동작이다. 모든 루프의 대기열을 비우고 익스큐터를 종료시킨다. asyncio.run() 내부에서는 호출될 때마다 신규 이벤트 루프를 생성하고 반환하기 전에 루프를 닫는다.

Asyncio의 계층

  • 계층 9: 네트워크: 스트림
  • 계층 8: 네트워크: TCP & UDP
  • 계층 7: 네트워크: 트랜스포트
  • 계층 6: 도구
  • 계층 5: 별개의 스레드와 프로세스
  • 계층 4: Task
  • 계층 3: Future
  • 계층 2: 이벤트 루프
  • 계층 1: 코루틴

코루틴


async def f():
  return 123

>>> type(f)
>>> import inspect
>>> inspect.iscoroutinefunction(f)
  • 위의 함수는 가장 간단한 형태의 코루틴 선언이다. 일반적인 함수와 유사해보이지만, async def 키워드로 시작한다는 점이 다르다.

  • 함수 f()의 정확한 타입은 ‘코루틴’이 아니라 코루틴 함수이다. 파이썬에서 제네레이터 함수의 형태와 동일하다.

  • 함수 g가 제네레이터로 불리는 경우가 있는데 위에서 보듯이 g 자체는 함수일 뿐이다.

  • 제네레이터는 호출하여 값으로 반환받아야 하며, 코루틴 함수도 이와 동일하다. async def 함수를 호출하여 코루틴 객체를 반환받아야 한다.

  • 그렇다면 코루틴은 무엇인가? 코루틴은 완료되지 않은 채 일시 정지 했던 함수를 재개 할 수 있는 기능을 가진 객체이다. 이는 제네레이터와 매우 흡사하다.

  • 파이썬 3.5에서 async defawait를 키워드를 이용하여 네이티브 코루티을 도입하기 전, 파이썬에 3.4에서는 제네레이터와 데코레이터를 통해서 asyncio 라이브러리를 사용할 수 있었다.

  • 파이썬에서 코루틴 객체들이 어떻게 사용되는지 좀 더 확인을 해보겠다. 가장 중요한 것은 파이썬의 코루틴 사이에서 실행을 ‘전환’ 하는 방식이다.

  • 코루틴이 반환할 때 실제로는 StopIteration 예외가 발생한다.

  • 코루틴에 None을 전달하여 초기화를 한다. 이벤트 루프는 내부적으로 동일한 방식을 통해서 코루틴에 대해 초기화를 진행하므로 직접 실행할 필요는 없다.

  • 생성한 모든 코루틴을 loop.create_task(coro) 혹은 await coro를 통해서 실행하면 loop 가 알아서 .send(None)를 내부적으로 실행할 것이다.

await 키워드

  • 새로운 키워드 await는 항상 매개변수를 하나 필요로 한다. 허용되는 형은 awaitable로 불리며 다음 중 하나여야 한다.

  • 코루틴 (즉, async def 함수의 반환값)

  • __await__() 라는 특별 매서드를 구현한 모든 객체, 이 메서드는 반드시 이터레이터를 반환해야한다.

import asyncio


async def f():
    await asyncio.sleep(0)
    return 123


async def main():
    result = await f()
    return result
  • 함수 f()를 호출하면 코루틴을 반환하고 이는 f()에 대해서 await() 할 수 있다는 뜻이다.

  • f()가 완료되면 result의 변수 값은 123이 될 것이다.

참고 문헌

>> Home