본문 바로가기
Language/Python

[Python] 코루틴으로 짜여있지 않은 함수 비동기적으로 이용하기(feat.run_in_executor) | LIM

by forestlim 2022. 7. 14.
728x90
반응형

파이썬에서 비동기 처리를 할 때 await 뒤에 오는 함수 역시 코루틴으로 작성되어 있어야 비동기적인 작업이 가능하다. 비동기로 짜여져 있는 라이브러리도 있으나 보통은 비동기를 고려하지 않고 짜여진 라이브러리가 대부분이다. 그럴 때 event loop 의 run_in_executor 함수를 이용하면 동기적인 함수를 비동기적으로 이용할 수 있다. 

 

✔️ 먼저, asyncio.get_event_loop()를 활용해서 현재 이벤트 루프를 받아온 후 이벤트 루프의 run_in_executor 를 사용한다. run_in_executor는 다음과 같이 정의되어 있다.

 

def run_in_executor(self, executor, func, *args):
    self._check_closed()
    if self._debug:
        self._check_callback(func, 'run_in_executor')
    if executor is None:
        executor = self._default_executor
        # Only check when the default executor is being used
        self._check_default_executor()
        if executor is None:
            executor = concurrent.futures.ThreadPoolExecutor(
                thread_name_prefix='asyncio'
            )
            self._default_executor = executor
    return futures.wrap_future(
        executor.submit(func, *args), loop=self)

 

첫번째 인자는 executor을 받아온다. executor를 따로 정의해주지 않으면 asyncio 내부의 concurrent.futures.ThreadPoolExecutor을 사용하게 된다. 두번째 인자로는 사용하고자 하는 함수, 그 이후의 인자는 사용하고자 하는 함수의 인자들을 써주면 된다. 

 

위에서 보다시피 executor를 따로 정의하지 않으면 ThreadPoolExecutor을 사용하게 되는데 ThreadPoolExecutor의 경우에는 스레드풀을 사용하여 호출을 비동기적으로 실행하는 Executor 서브 클래스이다. 즉, 비동기적 처리처럼 보이지만 실제로는 멀티스레딩을 사용하는 것이다. 

 

당연히 이전 작업이 끝나야 다른 작업이 실행될 수 있는 동기적인 작업 보다는 일찍 끝나지만 파이썬에서는 GIL 정책으로 스레드들이 동시에 작업이 불가능하기 때문에 다른 스레드를 호출하는데 걸리는 시간이 낭비되고 이를 컨텍스트 스위칭 비용이라고 한다. 

 

따라서 최대한 비동기적으로 이용할 수 있는 라이브러리가 있다면 그 라이브러리를 이용하는게 좋다. 대표적으로

 ✅ time.sleep() 대신 asyncio.sleep() 을 사용

 ✅ requests.get() 대신 aiohttp.ClientSession() 을 사용

 

참고) ThreadPoolExecutor에서 worker의 개수를 적어주지 않으면 다음과 같은 계산식에 따라 worker의 수가 부여된다.

max_workers = min(32, (os.cpu_count() or 1) + 4)
728x90
반응형

댓글