asyncio_이벤트_루프

매일 공부(ML)·2022년 8월 16일
0

이어드림

목록 보기
120/146

응답성을 최대로 높이려면 asyncio 이벤트 루프를 블록하지 말라

문제점

  • 출력 파일 핸들에 대한 open, close, write 호출이 주 이벤트 루프에서 이뤄진다.

  • 운영체제의 시스템 콜을 사용하므로 이벤트 루프를 상당히 오랫동안 블록할 수 있다.

  • 동시성이 아주 높은 서버에서는 응답 시간이 늘어날 수 있다.

#debug = True라는 파라미터를 asyncio.run함수로 넘기면 문제 발생의 감지 가능
#잘못 작성된 코루틴이 어떤 파일의 어느 줄에 있는지 알아내는 방법을 ㅂ보여줌
import time

async def slow_coroutine():
    time.sleep(0.5) #느린 I/O를 시뮬레이션함

asyncio.run(slow_coroutine(), debug = True)
#프로그램의 응답성을 최대로 높이기 위해서 이벤트 루프 안에서 시스템 콜이 이뤄질 잠재적 가능성 최소화
#모든 데이터를 기록하는 새로운 Thread하위 클래스 만들기

from threading import Thread

class WriteThread(Thread):
    def __init__(self, output_path):
        super().__init__()
        self.output_path = output_path
        self.output = None
        self.loop = asyncio.new_event_loop()

    def run(self):
        asyncio.set_event_loop(self.loop)
        with open(self.output_path, 'wb') as self.output:
            self.loop.run_forever()
            #맨 마지막에 한 번 더 이벤트 루프를 실행
            #다른 이벤트 루프가 stop()에 await하는 경우 해결
            self.loop.run_until_complete(asyncio.sleep(0))
#Lock를 사용할 필요가 없어짐

async def real_write(self, data):
    self.output.write(data)

async def write(self, data):
    coro = self.real_write(data)
    future = asyncio.run_coroutine_threadsafe(coro, self.loop)
    await asyncio.wrap_future(future)
#stop메서드를 사용하여 작업자 스레드에게 실행 중단

async def real_stop(self):
    self.loop.stop()

async def stop(self):
    coro = self.real_stop()
    future = asyncio.run_coroutine_threadsafe(coro, self.loop)
    await asyncio.wrap_future(future)
#with문과 함께 사용할 수 있도록 __aenter__와 __aexit__메서드를 정의
#주 이벤트 루프 스레드를 느리게 만들지 않으면서 제 시간에 시작하고 종료

async def __aenter__(self):
    loop = asyncio.get_event_loop()
    await loop.run_in_executor(None, self.start)
    return self

async def __aexit__(self, *_):
    await self.stop()
#WriteThread 클래스 사용 시 run_tasks를 완전히 비동기적인 버전으로 리팩터링
#비동기 버전은 읽기도 좋고, 주 이벤트 루프 스레드의 동작을 느리게하는 시스템 콜을 전혀 실행 안함

def readline(handle):
    ...
async def tail_async(handle, interval, write_func):
    ...
async def run_fully_async(handles, interval, output_path):
    async with WriteThread(output_path) as output:
        tasks = []
        for handle in handles:
            coro = tail_async(handle, interval, output.write)
            task = asyncio.create_task(coro)
            tasks.append(task)

        await asyncio.gather(*tasks)

Summary

  • 시스템 콜(블로킹 I/O와 스레드 시작도 포함)을 코루틴으로 만들면 프로그램의 응답성이 좋아지고 사용자가 느끼는 지연 시간을 줄일 수 있다.

  • debug = True 파라미터를 asyncio.run에 넘기면 이벤트 루프가 빨리 반응하지 못하게 방해하는 코루틴 식별

profile
성장을 도울 아카이빙 블로그

0개의 댓글