Language

[ python ] asyncio/coroutine/eventloop

궁금한게 많은 개발자 2023. 4. 16. 15:56

동시 프로그래밍(concurrent programming)은 기본적으로 여러 스레드를 활용하여 다중 스레드 프로그래밍을 뜻합니다.
하지만 다중 스레드 환경에서 thread-safe한 프로그래밍을 개발하는 것은 쉽지 않고 고려해야할 사항이 많습니다. 또한, 싱글 코어 프로세서에서 다중 스레드 프로그램을 돌리면 성능이 기대했던 것 보다 나오지 않는 문제가 있습니다.

 

그래서 단일 스레드에서 비동기적으로 동시에 여러 task를 처리하는 비동기 프로그래밍이 등장하게 되었습니다.

python3.4에서 asyncio가 표준 라이브러리로 채택이 되고, 3.5버전에서는 async/await 키워드가 문법으로 채택되면서 파이썬에서도 비동기 프로그래밍이 가능하게 되었습니다.

 

Event Loop 동작 원리

파이썬의 이벤트 루프는 단일 스레드에서 동작합니다. 이벤트 루프가 코루틴을 처리할 때에는 새로운 스레드나 프로세스를 생성하는 것이 아니라, 실행 될 코루틴들을 우선 순위에 따라 저장하는 큐에 넣어두고 실행 할 코루틴에 제어권을 넘긴다음 코루틴이 실행될 때 까지 이벤트루프는 대기합니다.

코루틴이 I/O바운드 작업을 요청해야하거나, 대기가 필요한 경우 다시 제어권은 이벤트 루프로 넘어와 다른 코루틴에게 제어권을 넘겨 비동기식으로 작업을 처리합니다. 이 과정에서 I/O바운드 작업은 제어권이 필요하지 않은 별도의 스레드나 프로세스에 의해 실행되고, 작업이 종료되면 다시 실행될 코루틴 큐에 들어가게 됩니다. 그렇게 반복적으로 큐에 있는 코루틴을들 처리하면서 하나의 스레드에서 이벤트루프는 여러 task들을 코루틴에 의해 비동기적으로 처리할 수 있게 됩니다.

 

즉, 이벤트 루프가 코루틴을 처리하는 동안, 별도의 스레드나 프로세스를 생성하지 않고 단일 스레드에서 비동기 처리를 가능하게 합니다. 따라서, 이러한 방식으로 비동기 프로그래밍을 구현하면 스레드 간의 경합 조건이나 데드락 등과 같은 복잡한 동시성 문제를 회피할 수 있고, 메모리 사용량도 줄일 수 있습니다. 하지만 이벤트 루프는 여전히 CPU 바운드 작업을 처리할 때는 한 번에 하나의 작업만 처리할 수 있으므로, CPU 바운드 작업을 비동기적으로 처리하는 것은 효율적이지 않을 수 있습니다.

python async programming

event loop에서 coroutine들을 동작하는 원리를 세부적으로 살펴보면, 일반적으로 caller함수에서 coroutine을 호출하면

반복적으로 next, send함수를 호출하며 yield에 멈춰있는 coroutine을 동작시킵니다.

또한 coroutineA내부에서 coroutineB를 호출하려면 yield from키워드를 사용하는데, caller에서 next, send를 호출하면 coroutineB에서 멈췄던 yield가 지나가게 됩니다. 이러한 next, send를 반복적으로 호출해주는 역할을 하는 것이 event loop입니다. python3.5부터는 async/await키워드를 문법으로 지원하면서 coroutine, yield키워드는 사용하지 않고, async키워드를 함수앞에 붙이거나 yield대신 await를 사용하면 됩니다.

 

Coroutine의 특징: 멀티 스레드를 대처하는 것이 아닌 일반적인 함수인 서브루틴처럼 진입점과 탈출점이 하나인 함수에서의 스레드 대기 시간을 최소화. (다양한 진입점과 탈출점)비 선점형 멀티테스킹으로 병렬이 아닌 병행. 동시에 실행되는 것 처럼 보이며 CPU사용권을 주고 받지않아서 작업 교환 비용 적음.