-
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 def
와await
를 키워드를 이용하여 네이티브 코루티을 도입하기 전, 파이썬에 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이 될 것이다.