🚨 더 구체적인 내용은 아래 링크에 아주 친절하게 잘 정리되어 있으니 꼭 링크가서 읽어주세요!!
https://it-eldorado.tistory.com/159?category=749661
대부분의 API는 비동기를 지원하지 않는 "동기"방식으로 동작한다.
왜냐, python 자체가 동기 방식으로 동작하도록 설계된 언어이기 때문이다.
실제 asyncio.sleep()
이 지원되기 이전에는 동기 함수인 time.sleep()
만 사용할 수 있었는데 time.sleep()
은 실행 흐름을 블록하는 블로킹 함수이다.
마찬가지로 request.get()
, request.post()
도 현재 실행을 블록하는 함수이다.
이러한 함수"만" 이용하면 결국 asyncio를 사용하게 되는 이유 자체가 명확하지 않게 된다. 비동기 프로그래밍이 가능하려면 그 작업을 다른 곳에 맡겨두고 Future객체를 await 하면서 실행중인 태스크의 제어를 이벤트 루프에게 넘겨주어야 하기 때문이다.
이때 사용하는 것이 loop.run_in_executor()
메서드인데 loop
는 이벤트 루프객체를 의미한다.
간단하게 얘기하자면 이 메서드는 동기 함수를 "별도의 쓰레드"에서 실행하기 때문에 비동기 함수처럼 사용할 수 있도록 해준다.
비동기 프로그래밍은 작업을 다른 어딘가(별도의 쓰레드)에 맡기는 것을 의미한다.
이 함수의 사용 방법을 알아보자. 이 함수의 반환 값은 퓨처 객체이기 때문에 await
키워드 뒤에 올 수 있다.
import asyncio
import time
async def sleep(sec):
await loop.run_in_executor(None, time.sleep, sec) # time.sleep(sec)
return sec
async def main():
sec_list = [1, 2]
tasks = [asyncio.create_task(sleep(sec)) for sec in sec_list] # [Task 1 객체, Task 2 객체]
tasks_results = await asyncio.gather(*tasks) # [Task 1 객체의 결과 값, Task 2 객체의 결과 값]
return tasks_results
start = time.time()
loop = asyncio.get_event_loop()
result = loop.run_until_complete(main())
loop.close()
end = time.time()
print('result : {}'.format(result))
print('total time : {0:.2f} sec'.format(end - start))
# 출력 결과
# result : [1, 2]
# total time : 2.03 sec
원래는 블로킹 함수인 time.sleep()
함수가 마치 asyncio.sleep()
함수처럼 동작할 수 있도록 하였다.
loop.run_in_executor()
메소드의 첫 번째 인자로 넘어가는 None
은 실행기를 명시적으로 지정하지 않고 기본 실행기를 사용하겠다는 것인데, 직접 실행기를 지정하면 워커 쓰레드를 원하는 개수만큼 생성하는 것이 가능하다.
두 번째 인자에는 함수 이름을 넘기고, 세 번째 인자부터는 그 함수를 호출할 때 넘길 인자들을 하나씩 넘기면 된다.