Python을 사용한 비동기 실행(Pt. 1)

비동기 패턴의 장점과 개념에 대한 일반적인 개요는 다음을 확인하세요.
  • Guide to Asyncio - Medium
  • JavaScript EventLoop - MDN
  • asyncio - PyDocs

  • Pythonasyncio 라이브러리를 사용하면 I/O 바인딩 전략을 위한 동시 코드를 작성할 수 있습니다. 이는 CPU 바운드 전략을 위한 스레드 사용에 중점을 둔 라이브러리multiprocessing와 다릅니다.
    asyncio/async 키워드와 함께 await 라이브러리는 "이벤트 루프"라는 개념을 사용하여 이를 가능하게 합니다. 이는 비동기 루프 및 기능을 생성하고 예약하는 추상적인 방법을 제공합니다. 브라우저의 프런트 엔드 상호 작용에 사용되는 JavaScript도 이벤트 루프 개념을 기반으로 하므로 이 개념을 비교/검토하기에 완벽한 언어입니다.

    Note: Some of the asyncio functions implemented in Python 3.7+ may be Deprecated in Python 3.10+



    동기식 실행



    일반적으로 Python 및 대부분의 다른 절차적 언어로 프로그래밍할 때 프로그램은 위에서 아래로 실행됩니다(코드가 구조화되지 않은 경우에도 언어 해석/컴파일러 참조). 즉, 각 문은 이전 문(들)이 완료된 후에 실행됩니다.

    예를 들어, 지정된 random_add(초 단위) 다음에 임의의 정수 목록을 합산하는 delay 함수가 있다고 가정합니다.

    def random_add(delay: int) -> None:
        import time
        time.sleep(delay)
        # This retrieves a list of random ints
        n = random_number_list()
        print(f'Sum({n}) = {sum(n)}')
    


    이 함수를 연속해서 3번 실행합니다.

    if __name__ == "__main__":
        random_add(5) # call1: Delayed 5s
        random_add(8) # call2: Delayed 8s
        random_add(2) # call3: Delayed 2s
    


    총 타이밍은 15초에 가깝습니다. 다음과 같이 각 호출이 하나씩 완료됩니다below.
    random_add 함수가 지연된 기간 동안 계산을 수행하지 않더라도 해당 기간 동안 할당된 리소스가 여전히 있습니다. 이것이 일반적인 동기식 프로그램의 시간 소비가 일반적으로 모든 프로시저의 합계로 설명될 수 있는 이유입니다.

    동기 타이밍




    전화
    시작
    마치다
    주문 완료


    1
    0초
    5초
    1

    2
    5초
    13초
    2


    13초
    15초



    비동기 실행



    이전random_add 함수를 재사용하고 비동기적으로 실행하도록 조정할 수 있습니다.

    import asyncio
    async def random_add(delay: int) -> None:
        asyncio.sleep(delay)
        # This retrieves a list of random ints
        n = random_number_list()
        print(f'Sum({n}) = {sum(n)}')
    


    이제 이전과 마찬가지로 이 함수를 3번 실행합니다.

    if __name__ == "__main__":
        await random_add(5)
        await random_add(8)
        await random_add(2)
    


    어 오! 다음과 유사한 SyntaxError를 수신해야 합니다.

        await random_add(5)
        ^^^^^^^^^^^^^^^^^^^
    SyntaxError: 'await' outside function
    


    이 문제는 Python 스크립트를 실행하기 전에 사용 중인 IDE에서 감지되었을 수도 있습니다. awaitasync 범위에서만 사용할 수 있고 main 스레드는 이벤트 루프 없이 동기식이기 때문입니다! 이 코드를 실행하려면 다음을 수행해야 합니다.

  • 이 코드를 async 함수로 래핑합니다.

    async def async_test():
        await random_add(5)
        await random_add(8)
        await random_add(2)
    


  • 그런 다음 asyncio.run 기능을 활용하십시오.

    if __name__ == "__main__":
        asyncio.run(async_test())
    


  • 이제 코드가 행복하게 실행되어야 합니다. 그러나 코드를 실행하는 데 여전히 동일한 시간이 걸립니다. 왜 그런 겁니까? async 병렬로 작동하지 않습니까?

    그 이유는 await 키워드가 상위 함수가 다음 명령문으로 계속 진행하기 전에 결과를 대기함을 의미하기 때문입니다. 응답이 지연되므로 해당 기간 동안 대기합니다.
    random_add 함수 호출을 동시에 실행하려면 실행할 Task 를 만들어야 합니다. 이는 asyncio.create_task를 통해 개별적으로 수행할 수 있습니다.

    async def async_test_task():
        task1 = asyncio.create_task(random_add(5))
        task2 = asyncio.create_task(random_add(8))
        task3 = asyncio.create_task(random_add(2))
        await task1, await task2, await task3
    


    또는 작업 목록을 보려면 asyncio.gather를 통해:

    async def async_test_gather():
        await asyncio.gather(
            random_add(5),
            random_add(8),
            random_add(2)
        )
    


    이 게시물의 나머지 부분에서는 단순화를 위해 asyncio.gather를 활용합니다. asyncio.create_task를 적용할 수 있는 특정 시나리오(예: 가변 작업 생성)가 있습니다.

    이제 코드가 조정되었으므로 총 실행 시간이 가장 느린 작업에 의해서만 제한된다는 것을 알 수 있습니다! 또한 각 작업이 완료되는 순서도 다릅니다(표시됨below ).

    비동기 타이밍




    전화
    시작
    마치다
    주문 완료


    1
    0초
    5초
    2

    2
    ~0초
    8초



    ~0초
    2초
    1


    이것은 Python의 async/awaitasyncio 라이브러리에서 제공하는 기능의 시작일 뿐입니다. 이 개요가 릴리스되면 이 개요의 파트 2를 반드시 읽으십시오.

    좋은 웹페이지 즐겨찾기