[Python] 비동기IO의 async/await(asyncio)를 사용하는 방법


Study/Python  2020. 1. 18. 09:00

안녕하세요. 명월입니다.


이 글은 Python에서 비동기IO의 async/await(asyncio)를 사용하는 방법에 대한 설명입니다.


async/await는 비동기 처리기법으로 Thread로 어떠한 함수를 실행했을 때 그에 대한 결과를 받기가 엄청 애매합니다.

링크 - [Python] 쓰레드(Thread)과 lock 그리고 데드락

async/await는 C#에서도 비슷한 문법이 있는데 참고하시면 이해하는데 도움이 됩니다.

링크 - [C# 강좌 - 55] 키워드 async, await

import threading, time;
# 결과를 담을 변수
ret = 0;
def example():
  global ret;
  for i in range(1,10,1):
    time.sleep(0.1);
    ret += i;
# example함수를 Thread로 개시한다.
th1 = threading.Thread(target=example);
th2 = threading.Thread(target=example);
th1.start();
th2.start();
# 쓰레드가 종료할 때까지 기다림.
th1.join();
th2.join();
# 결과 출력
print(ret);

위처럼 Thread를 구현해서 값을 가져올 수 있습니다. 무언가 소스가 좀 난잡한 느낌입니다.

전역 변수로 ret를 만들어서 데이터를 넣게끔 만들었습니다만, 만약 각 쓰레드마다 파라미터 값의 의해 다른 값을 나오게 해야한다면 아마 엄청 복잡해지게 될 것입니다.

위와 같은 비동기처리를 깔끔하게 처리할 수 있는 방법이 async/await(asyncio)입니다.

# async/await를 사용하기 위한 라이브러리
import asyncio;

# 비동기를 지정하기 위한 example();
async def example():
  ret = 0;
  for i in range(1,10,1):
    await asyncio.sleep(0.1);
    ret += i;
  return ret;

async def main():
  # example함수를 비동기로 개시한다. 
  t1 = asyncio.create_task(example());
  t2 = asyncio.create_task(example());
  
  # t1와 t2가 종료가 되면 결과값을 더한다.
  print(await t1 + await t2);

#async def를 실행시키기 위한 함수.
asyncio.run(main());

async와 await를 사용하게 되면 threading을 사용할 때 보다 더 깔끔해 보입니다.


먼저 async는 함수의 앞에 사용되는 키워드입니다. 외부로는 비동기를 실행시키기 위한 키워드이고 내부적으로는 await을 사용할 있게하는 예약어입니다.

main함수에서 t1과 t2를 기다리는 await를 사용했는데 만약 main이 async가 아니거나 단순 함수 내부가 아닌 전역 영역에서 await를 사용하게 되면 에러가 발생하게 됩니다.

await의 키워드는 기다리는 뜻의 키워드로 await asyncio.sleep(i)의 경우는 i는 초단위로 time.sleep와 같은 의미이고 create_task가 된 task오브젝트 앞에 await을 사용하게 되면 쓰레드가 종료될 때까지 기다리는 thread.join과 같은 의미의 키워드입니다.


마지막으로 asyncio.run는 async를 create_task처럼 비동기식이 아닌 동기식(프로세스의 순차적 처리)으로 처리하게 되는 호출입니다.

asyncio.run를 사용해도 괜찮으나 최근 python에서는 run_until_complete를 사용하기를 권장하고 있습니다.


또 create_task로 비동기식으로 나누었으나 asyncio.gather로도 비동기 처리가 가능합니다.

# async/await를 사용하기 위한 라이브러리
import asyncio;

# 비동기를 지정하기 위한 example();
async def example():
  ret = 0;
  for i in range(1,10,1):
    await asyncio.sleep(0.1);
    ret += i;
  return ret;

async def main():
  # example함수를 비동기로 개시한다. 파라미터에 두개를 넣는 것으로 비동기를 두번 실행하는 것으로 의미한다.
  ret = await asyncio.gather(example(), example());
  print(sum(ret));

#async def를 실행시키기 위한 함수.
loop = asyncio.new_event_loop();
asyncio.set_event_loop(loop);
try:
  loop.run_until_complete(main());
finally:
  loop.close();
  asyncio.set_event_loop(None);

사실 저도 async/await를 자주 사용하는 문법이 아니기 때문에 위 문법이상으로 사용할 일은 없네요.

참조 - https://docs.python.org/3/library/asyncio-task.html

참조 - https://docs.python.org/3/library/asyncio-eventloop.html

참조 - https://stackoverflow.com/questions/55590343/asyncio-run-or-run-until-complete


여기까지 Python에서 비동기IO의 async/await(asyncio)를 사용하는 방법에 관한 설명이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.