이벤트 루프란 무엇인가?
비동기 프로그래밍의 중심에는 이벤트 루프가 있다. 이는 단일 스레드 내에서 코루틴을 관리하고 실행하는 환경이다. 이벤트 루프는 비동기 작업의 흐름을 제어하며, 다음과 같은 기능을 수행한다:
- 코루틴의 실행 및 진행
- 콜백 함수 호출
- 네트워크 입출력 처리 (예: 소켓 통신)
- 서브프로세스 관리
asyncio 모듈은 이벤트 루프에 접근하고 조작할 수 있는 인터페이스를 제공한다. 그러나 일반 애플리케이션 개발자에게는 직접적으로 사용할 필요가 거의 없다. 대부분의 경우, 고수준의 async/await 문법만으로 충분하다.
이러한 저수준 인터페이스는 주로 라이브러리나 프레임워크 개발자가 asyncio 기반 시스템을 확장하거나 통합할 때 활용된다.
이벤트 루프 생성과 접근 방법
일반적으로 asyncio.run() 함수를 통해 이벤트 루프를 시작한다. 이 함수는 입력된 코루틴을 실행하고, 완료될 때까지 블록킹하며, 이후 루프를 정리한다.
필요 시, 직접 새로운 이벤트 루프를 생성할 수도 있다. 이를 위해 asyncio.new_event_loop() 함수를 사용할 수 있다.
import asyncio
# 새로운 이벤트 루프 생성
custom_loop = asyncio.new_event_loop()
print(custom_loop)
실행 결과 예시:
<_UnixSelectorEventLoop running=False closed=False debug=False>
이 출력은 현재 루프가 실행 중이지 않고, 닫히지도 않았으며 디버그 모드도 꺼져 있음을 의미한다.
이미 실행 중인 이벤트 루프에 접근하고 싶다면, asyncio.get_running_loop()를 사용하면 된다. 이 함수는 현재 스레드에서 활성화된 루프를 반환하며, 이외의 상황에서는 예외를 발생시킨다.
또한 asyncio.get_event_loop()라는 함수도 존재하지만, 파이썬 3.10부터는 사용이 권장되지 않으며 폐기 예정이다. 대신 위의 두 함수를 사용해야 한다.
이벤트 루프 객체의 구현 방식
이벤트 루프는 실제 객체로 구현되며, AbstractEventLoop 클래스에 의해 정의된 공통 인터페이스를 따르며, 플랫폼에 따라 다양한 구현체가 존재한다.
- Unix 기반 시스템 (Linux, macOS): 기본적으로
SelectorEventLoop를 사용. 이는epoll또는kqueue등의 선택적 메커니즘을 기반으로 비차단 입출력을 처리한다. - Windows:
ProactorEventLoop가 기본 설정. 이는 Windows의I/O Completion Port기반으로 설계되어 있으며, 비동기 파일/네트워크 입출력에 최적화되어 있다.
특정 목적을 위한 성능 최적화가 필요한 경우, 제3자 라이브러리에서도 자체 이벤트 루프를 구현할 수 있다.
왜 이벤트 루프에 접근해야 하는가?
이벤트 루프에 접근하는 이유는 다음과 같다:
- 실행 중인 태스크의 진행 상황을 실시간으로 모니터링
- 외부에서 코루틴을 큐잉하고 결과를 수집
- 한 번만 실행하고 종료되는 태스크를 생성 후 무시 (데몬형 작업)
또한 이벤트 루프는 일반적인 스레드 풀보다 더 효율적인 비동기 작업 처리 기반으로 활용될 수 있다. 특히, 복잡한 비동기 로직이 포함된 어플리케이션 내부에서 이벤트 루프를 임베딩하여 유연하게 조작할 수 있다.