파이썬 3.5이상에서는 Coroutine을 좀 더 계획적이고 더 나은 환경을 제공한다. 파이썬에 내장된 asyncio 패키지를 사용하는 것이다.
asyncio는 async/await 구문을 사용하여 동시성 코드를 작성하는 라이브러리이다.
동기 방식
def
키워드로 선언하는 모든 함수는 기본적으로 동기 방식으로 동작한다.def do_sync(): pass
비동기 방식
def
키워드 앞에 async
키워드를 붙여주면 해당 함수는 비동기 처리되며, 이러한 비동기 함수를 파이썬에서는 코루틴이라고 부른다.async def do_async():
pass
do_async() # <coroutine object do_async at 0x1038de710>
async
로 선언된 다른 비동기 함수 내에서 await
키워드를 붙여서 호출해야 합니다.async def main_async():
await do_async()
loop = asyncio.get_event_loop()
loop.run_until_complete(main_async())
loop.close()
asyncio.run(main_async())
객체가 await 표현식에서 사용될 수 있을 때 어웨이터블 객체라고 말한다.
어웨이터블 객체에는 코루틴, 태스크, 퓨처 세 가지 주요 유형이 있다.
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')
await say_after(2, 'world')
await say_after(2, 'python')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
# started at 15:13:45
# hello
# world
# python
# finished at 15:13:50
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
task3 = asyncio.create_task(say_after(2, 'python'))
print(f"started at {time.strftime('%X')}")
await task1
await task2
await task3
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
# started at 15:12:26
# hello
# world
# python
# finished at 15:12:28
위 코루틴 코드와 태스크를 비교해보자면 코루틴은 총 5초가 걸리고, 태스크는 2초가 걸리는 것을 확인할 수 있다. 그 이유는 async def로 선언 된 함수를 호출하면, 코드가 실행 되지 않고 코루틴 객체를 리턴하기만 할 뿐이기 때문이다. 그리고 create_task는 이 반환된 객체를 가지고 비동기 작업 객체인 태스크를 만들고, 실행한다.
async def안에 작성한 코드가 실행되는 시점은 코루틴에서는 await say_after(1, "hello")
이고, say_after(1, "hello")
호출로 코루틴 객체가 생성되고, await에 의해 함수 내 작성한 코드가 동기적으로 실행되면서 끝날 때까지 대기한다. 때문에 먼저 1초, 2초, 2초 순차적으로 기다린다.
반면에 태스크는 task1 = asyncio.create_task(say_after(1, 'hello'))
이 부분에서 이미 코드를 비동기로 실행했다. 그리고 await task 는 그저 이미 실행한 코드를 대기할 뿐이다. 따라서 task1, task2, task3의 await 순서를 바꿔도 같은 결과를 얻는다.
동시에 여러 태스크(코루틴) 실행하기
awaitable asyncio.gather(*aws, loop=None, return_exceptions=False)
인자로 넘어온 모든 어웨이터블이 성공적으로 완료되면, 결과는 반환된 값들이 합쳐진 리스트를 반환한다. (결괏값의 순서는 aws에 있는 어웨이터블의 순서와 일치)
import asyncio
import time
async def coroutine1():
await asyncio.sleep(1)
print("Hello coroutine1!")
return "coroutine1"
async def coroutine2():
await asyncio.sleep(2)
print("Hello coroutine2!")
return "coroutine2"
async def coroutine3():
await asyncio.sleep(1)
print("Hello coroutine3!")
return "coroutine3"
async def main():
print(f"started at {time.strftime('%X')}")
# 코루틴 실행 결과(리턴값)가 coroutine_list 변수에 list로 담긴다.
coroutine_list = await asyncio.gather(coroutine1(), coroutine2(), coroutine3())
print(coroutine_list)
print(f"finished at {time.strftime('%X')}")
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# started at 15:50:19
# Hello coroutine1!
# Hello coroutine3!
# Hello coroutine2!
# ['coroutine1', 'coroutine2', 'coroutine3']
# finished at 15:50:21
이벤트 루프는 모든 asyncio 응용 프로그램의 핵심이다. 이벤트 루프는 비동기 태스크 및 콜백을 실행하고 네트워크 IO 연산을 수행하며 자식 프로세스를 실행한다.
이벤트 루프는 작업들을 루프(반복문)를 돌면서 하나씩 실행시키는 역할을 한다. 이때, 만약 실행된 작업이 특정한 데이터를 요청하고 응답을 기다려야 한다면, 이 작업은 다시 이벤트 루프에 통제권을 넘겨준다. 통제권을 받은 이벤트 루프는 다음 작업을 실행하게 된다. 그리고 응답을 받은 순서대로 멈췄던 부분부터 다시 통제권을 가지고 작업을 마무리한다.
asyncio.run(coro, *, debug=False)
전달 된 코루틴을 실행하고, asyncio의 event loop를 관리한다. 같은 thread에서 다른 asyncio event loop가 실행중일 경우에는 실행할 수 없다. Python 3.7에서 새로 추가되었다.
asyncio.create_task(coro)
전달 된 코루틴을 wrapping하여 Task object를 만들고 schedule 한다. Python 3.7에서 새로 추가되었고, 이전 Python에서는 asyncio.ensure_future()를 사용한다.
asyncio.sleep(delay, result=None, loop=None)
delay초 동안 대기한다. result가 주어진 경우, sleep이 완료된 후 해당 값을 caller에게 리턴한다. sleep() 함수는 현재 task를 suspend 시키므로, 이 시점에 다른 task가 실행될 수 있다.
asyncio.gather(*aws, loop=None, return_exceptions=None)
주어진 awaitable objects들을 concurrent하게 실행한다. 주어진 awaitable object 중 코루틴이 있으면, 자동으로 Task로 schedule된다. 모든 awaitable들이 정상적으로 끝나면, 각 awaitable의 return value의 list가 return 된다. 순서는 aws의 순서와 동일하다.
asyncio.wait_for(aw, timeout, *, loop=None)
주어진 awaitable을 timeout초 동안 기다린다. aw가 코루틴 경우, 자동으로 Task로 schedule 된다. 해당 시간이 지나면, asyncio.TimeoutError가 발생한다.
자세한 내용은 아래 참고.
저수준 API
고수준 API
참고
https://kdw9502.tistory.com/6
https://sjquant.tistory.com/15