ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 파이썬 비동기 프로그래밍 - (1) 코루틴
    개발 공부의 시작/python 2024. 1. 18. 18:30
    728x90

    전통적으로 동시 프로그래밍은 멀티 스레드를 활용해서 이뤄진다. 하지만 thread-safe한 프로그램을 작성하는 것은 쉬운 일이 아니다.

    싱글 코어 프로세서에서 프로그램을 실행하면 스레드를 늘리면 늘릴 수록 잔여 스레드의 컨텍스트 오버헤드와 동기화 등의 문제가 발생한다. 따라서 동시 처리에 대한 성능 향상이 저하되는 문제가 발생한다. 이러한 이유로 싱글 스레드로 동시 처리를 하는 비동기 프로그래밍이 활용된다.

     

    비동기 프로그래밍

    I/O 작업, 네트워크 요청, 파일 시스템 작업과 같은 시간이 많이 소요되는 작업을 효율적으로 처리한다. 작업이 완료되기를 기다리는 동안 다른 작업을 진행할 수 있도록 해서 응답성을 높이고 자원 사용을 최적화한다.

    일반적으로 이벤트 기반(event-driven)이나, 콜백(callback) 기반으로 구현된다.

     

    파이썬에서 비동기 프로그래밍

    웹 서버와 같은 어플리케이션을 개발하면 CPU 연산 대비 DB나 API와 연동 과정에서 발생하는 대기 시간이 훨씬 길다.

    비동기 프로그래밍은 네트워크 통신이나 파일 입출력에 발생하는 대기 시간을 낭비하지 않고, CPU가 다른 처리를 할 수 있도록 한다. 이를 non-blocking이라고 한다.

    파이썬은 기본적으로 동기 방식으로 동작하는 언어이지만, 3.4버전에서 asyncio모듈이 추가되고, 3.5에서 async/await 키워드가 문법으로 채택된다. 이를 통해 파이썬도 외부 라이브러리 없이 비동기 프로그래밍이 가능해졌다.

     

    코루틴이란?

    코루틴이랑 특정 시점에 자신의 실행과 관련된 상태를 어딘가에 저장한 뒤 실행을 중단하고, 나중에 그 상태를 복원하여 실행을 재개할 수 있는 서브루틴을 의미한다. 서브 루틴은 일반적으로 알고 있는 함수를 의미한다고 보면 된다.

    코루틴과 서브 루틴은 파이썬에서만 사용하는 용어가 아닌 CS 전반에서 사용되는 용어이다.

    일반적으로 메인 함수는 서브 루틴을 호출한 뒤 서브 루틴의 작업이 끝날 때까지 기다린다. 만약 서브 루틴이 파일 IO 혹은 대용량 파일 다운로드 같은 작업을 수행한다면, 서브 루틴이 작업을 마칠 때까지 기다려야 한다.

     

    메인 루틴에서 서브 루틴을 호출하면 서브 루틴의 코드를 실행한 뒤 다시 메인 루틴으로 돌아온다. 특히 서브 루틴이 끝나면 서브 루틴의 내용이 모두 사라진다.

    이 기다림을 보완해주는 것이 코루틴이다. 코루틴은 cooperative routine으로 서로 협력하는 루틴이라는 뜻이다. 메인 루틴과 서브 루틴처럼 종속된 관계가 아닌 서로 대등한 관계이며 특정 시점에 상대방의 코드를 실행한다.

     

     

    파이썬에서 코루틴과 서브루틴의 구현

    파이썬에서 서브 루틴과 코루틴은 다음과 같이 정의된다. def 키워드만 이용해서 함수를 정의하면 서브 루틴이 되고, 앞에 async 키워드를 붙여서 함수를 정의하면 코루틴이 된다.

    # Subroutine (Synchronous Function)
    def subroutine():
        print('subroutine')
    
    # Coroutine (Asynchronous Function)
    async def coroutine():
        print('coroutine')

     

    async 키워드에서 알 수 있듯 코루틴은 비동기 함수이다. 비동기는 어떤 작업이 완료되기를 기다리지 않고 그 시간 동안 다른 작업을 하는 것을 의미한다.

    일반적인 파이썬 프로그램은 동기 함수로 이뤄져 있기 때문에 어떤 작업이 완료되고 나서 다음 작업을 진행한다. 코루틴을 사용하면 비동기 코드를 작성할 수 있으므로 코루틴을 비동기 함수라고도 부르는 것이다.

     

     

    파이썬에서 코루틴은 제너레이터를 기반으로 구현된다. 즉 파이썬에서 코루틴은 곧 제너레이터이다. 제너레이터가 yield 키워드를 breakpoint 삼아 실행을 중단하고 재개하는 특징을 가지고 있기 때문이다.

     

    실제로 async/await가 등장하는 3.5 이전 버전에서는 코루틴을 직접 제너레이터 기반으로 작성해야 했다. async/await를 통해 제너레이터를 조금 더 쉽게 작성할 수 있게 되었다.

    기존에는 yield from 키워드 뒤에 제너레이터 객체를 두는 방식으로 또다른 제너레이터를 호출했다. await 키워드는 이 구문을 더 쉽게 작성할 수 있는 문법이다. await 키워드 뒤에는 코루틴 객체뿐 아니라 awaitable 객체라면 무엇이든 올 수 있다. 이러한 경우 해당 메소드를 호출해서 제너레이터 객체를 얻고 해당 제너레이터를 실행하는 방식으로 동작한다.

    https://docs.python.org/3/library/asyncio-task.html#awaitables

     

    Coroutines and Tasks

    This section outlines high-level asyncio APIs to work with coroutines and Tasks. Coroutines, Awaitables, Creating Tasks, Task Cancellation, Task Groups, Sleeping, Running Tasks Concurrently, Eager ...

    docs.python.org

     

    따라서 제너레이터를 호출하면 제너레이터 객체가 생성되어 반환되는 것 처럼 코루틴을 호출하면 제너레이터 객체와 유사한 코루틴 객체라는 것이 생성되어 반환된다.

    # 비동기 함수 (코루틴) 정의
    async def my_coroutine():
        return "Hello, Coroutine!"
    
    
    # 코루틴 객체 생성
    coroutine_obj = my_coroutine()
    
    # 코루틴 객체 확인
    print(
        "Coroutine Object:", coroutine_obj
    )  # Coroutine Object: <coroutine object my_coroutine at 0x7f048700e980>
    print(
        "Type of Coroutine Object:", type(coroutine_obj)
    )  # Type of Coroutine Object: <class 'coroutine'>

     

    자바스크립트에서 비동기 함수를 호출할 때 Promise 객체와는 조금 다르다. 자바스크립트에서는 비동기 함수를 호출하면 실제로 비동기 함수의 코드가 실행되면서 Promise 객체가 반환된다.

    하지만 파이썬에서는 코루틴을 호출해도 코루틴 객체만 반환될 뿐 그 코루틴의 코드가 실행되지 않는다.

     

    제너레이터와 코루틴은 다음과 같은 대응 관계를 가진다.

     

    728x90

    '개발 공부의 시작 > python' 카테고리의 다른 글

    [Python] FAST API에서 async def, def의 차이  (0) 2024.01.18
    Iterators와 Generators  (1) 2024.01.11
    [Python] Del의 시간복잡도  (1) 2023.12.03
Designed by Tistory.