본문 바로가기
Python

Python - asyncio 코루틴 await 이해하기

by 올엠 2024. 3. 25.
반응형

asyncio란

파이썬 3.5 버전부터 지원하기 시작한 코루틴을 사용할 수 있도록 지원해주는 라이브러리이다.

asyncio를 사용하기 위해선 함수 앞에 async를 붙여서, 코루틴으로 만들 수 있다. 

그리고 코루틴을 사용하고자 한다면, 해당 로직 앞에 await을 붙이면 된다.

즉 await를 붙인 코드에 대해  I/O 대기 등이 있을 경우 다른 코드 라인을 실행하는 구조 이다.

이때 await 뒤에 오는 코드는 코루틴으로 작성된 코드여야 한다. 

예를 들어 await 뒤에 time.sleep과 같이 사용한다면 스레드가 중단된다, 코루틴으로 동작하려면, asyncio.sleep을 사용해야 한다.

특정 모듈의 경우 await을 붙여도 코루틴으로 동작하지 않으므로 await 호환성 체크를 해보기 바란다.(Django)

아례 예제를 보면 asyncio를 사용하지만 run 코드에서 await를 붙이지 않아서 실행 속도는 개선이 되지 않고 10초가 소요된다.

동기 실행

먼저 가장 기본적인 동기실행 프로그램이다. 아래 프로그램을 실행하면, 6초의 시간이 걸리기 된다.

import time



def synctask1():
    print('task1 start')
    time.sleep(1)
    print('task1 end')


def synctask2():
    print('task2 start')
    time.sleep(1)
    print('task2 end')


start = time.time()
synctask1()
synctask2()
end = time.time()
runtime = end - start
print(f'실행 시간: {runtime}')

 

 

비동기 실행 코루틴 실행하기

async를 통해 await를 사용한다는 것 비동기를 실행하는 것이 아니라는 점을 명심하기 바란다.

실제 코루틴으로 동작하기 위해서는 태스크를 만들어 코루틴을 예약 해야 동작한다.

아래는 간단하게 테스크를 생성하는 행위이다.

import time
import asyncio


async def synctask1():
    print('task1 start')
    await asyncio.sleep(3)
    print('task1 end')


async def synctask2():
    print('task2 start')
    await asyncio.sleep(3)
    print('task2 end')


async def main():
    asynctask1 = asyncio.create_task(synctask1())
    asynctask2 = asyncio.create_task(synctask2())
    await asynctask1
    await asynctask2


start = time.time()
asyncio.run(main())
end = time.time()
runtime = end - start
print(f'총 실행 시간: {runtime}')
위 방식은 이해해보면, 먼저 코루틴으로 테스크 2개를 생성하고, 이를 시작하는 하는 asyncio 를 실행하는 방식이다. 코루틴으로 동작하면, await가 들어간 코드 혹은 asyncio에서 제공하는 비동기함수를 사용할 때 (Task)테스크 기반으로 비동기 적으로 수행는 방식으로 코드를 처리하게 되는데, 이외에도 테스크를 관리할 수 있는 방안이 몇가지 존재하는데 이에 대해서 알아보도록 하겠다.

gether / ensure_future()

위는 테스크를 만들고 별도로 대기 함수 await를 사용하는 등의 추가 작업 처리를 해주었다.  이보다 코루틴 내장 함수를 이용하여 대기 이벤트를 받을 수 있도록 구성할 수 있는데, asyncio.gether를 이용하면 된다. gether는 내부적으로 ensure_future()를 사용하기 때문에 ensure_future를 사용하여도 효과는 동일하다.

import time
import asyncio


async def synctask1():
    print('task1 start')
    await asyncio.sleep(3)
    print('task1 end')


async def synctask2():
    print('task2 start')
    await asyncio.sleep(3)
    print('task2 end')


async def main():
    await asyncio.gather(synctask1(), synctask2()) 


start = time.time()
asyncio.run(main())
end = time.time()
runtime = end - start
print(f'총 실행 시간: {runtime}')
 
asyncio의 run 함수를 살펴보면, 내부적으로 이벤트루프를 호출하여 실행이 완료될 때까지 기다리는 동작을 하도록 구성되어 있기 때문에 loop를 굳이 별도로 실행할 필요는 없다. 
간결한 코드를 작성하고자 한다면, run을 활용하자.


 
 
반응형