본문 바로가기
Dev/Python

[Python] 이벤트 루프 (Event Loop): 비동기 작업들의 실행 순서를 관리하고 제어하는 핵심 엔진

by Yoon_estar 2025. 10. 11.
728x90
반응형

파이썬은 전통적으로 동기적인 프로그래밍 언어였지만, asyncio 모듈의 등장과 함께 이벤트 루프(Event Loop)를 핵심으로 하는 비동기 프로그래밍의 강력한 플레이어로 자기매김했습니다. Node.js의 이벤트 루프가 자바스크립트의 싱글 스레드 한계를 극복하기 위해 태어났다면, 파이썬의 이벤트 루프는 고성능 네트워크 I/O 작업을 효율적으로 처리하기 위한 해답입니다.

개요 : 왜 파이썬에 이벤트 루프가 필요한가?

파이썬의 이벤트 루프는 싱글 스레드 내에서 여러 코루틴(Coroutine)의 실행을 관리하여 논블로킹(Non-blocking) 방식으로 동시성(Concurrency)을 구현하는 핵심 메커니즘입니다.

 

일반적인 동기 파이썬 코드는 파일 읽기, 웹 요청 등 I/O 작업이 발생하면 해당 작업이 완료될 때까지 전체 프로그램의 실행을 블로킹(Blocking) 합니다. 이벤트 루프는 이 블로킹 시간을 활용하여 다른 대기 중인 작업을 실행함으로써 자원의 낭비 없이 높은 처리량을 달성하게 해줍니다.

코루틴과 이벤트 루프의 관계

  • 코루틴(Coroutine) : 파이썬에서 async/await  키워드로 정의되는 비동기 함수입니다. 일반 함수와 달리, 실행 중간에 제어권을 자발적으로 포기하고(await) 나중에 다시 실행될 수 있는 특별한 함수입니다.
  • 이벤트 루프 : 코루틴들을 감시하고, 한 코루틴이 $\texttt{await}$를 통해 일시 정지(I/O 대기)할 때, 그 시간에 다른 대기 중인 코루틴에게 CPU 제어권을 넘겨주는 스케줄러 역할을 수행합니다.

용어 정리

파이썬의 비동기 프로그래밍은 asyncio 모듈과 그 구성 요소에 의존합니다.

용어 IT 용어 개념(파이썬 관점)
Event Loop 코루틴의 실행 흐름을 관리하고 I/O 이벤트를 처리하는 핵심 엔진
Coroutine async def로 정의된 비동기 함수 실행 중간에 $\texttt{await}$를 통해 제어권을 이벤트 루프에 양보할 수 있습니다. 
Task(태스크) 코루틴을 이벤트 루프가 실행할 수 있도록 포장한 객체. 코루틴의 실행 상태를 추적하고, 겨로가를 관리합니다.
Future(퓨처) 비동기 작업의 미래 결과를 나타내는 객체. 작업이 아직 완료되지 않았더라도 이 객체를 참조할 수 있습니다.
await 코루틴 내에서 사용되며, 비동기 작업(Task나 Future)이 완료되기를 기다리면서 이벤트 루프에 제어권을 양보하는 키워드
I/O Bound CPU 작업보다 입력/출력(네트워크,디스크)에 시간을 더 많이 소비하는 작업.(이벤트 루프가 가장 효과적인 영역)

 

실제 작동 원리 및 예시

파이썬의 이벤트 루프틑 자바스크립트와 달리 콜스택(Call Stack) 대신 코루틴의 상태 전이를 기반으로 작동합니다. 

초기화 및 코루틴 등록

  1. 이벤트 루프 생성 : asyncio.get_event_loop() 등으로 이벤트 루프 인스턴스를 얻습니다. 
  2. 코루틴 준비 : async 함수를 정의하고, $texttt{await}$를 사용해 비동기 작업을 예약합니다.
  3. Task로 감싸기 : $\texttt{asyncio.reate_task()_$를 사용해 코루틴을 Task로 감싸서 이벤트 루프에 실행을 요청합니다.

루프 실행과 제어권 양보

  1. Task 실행 : 이벤트 루프는 준비된 Task 중 하나를 선택하여 실행합니다.
  2. await 지점 : Task 실행 중 await 키워드를 만나면, 해당 코루틴은 일시 정지되고, 이벤트 루프는 이 Task 제어권을 회수합니다.
  3. 다른 Task로 전환 : 이벤트 루프는 대기 중인 다른 Task로 즉시 전환하여 실행합니다. (블로킹이 발생하지 않음)
  4. I/O 이벤트 감지 : 일시 정지된 Task가 기다리던 I/O 작업(예 : 네트워크 데이터 수신)이 완료되면, 이벶ㄴ트 루프는 이를 감지하고 해당 Task를 실행가능 (Ready) 상태로 만듭니다.

작업 완료 및 루프 종료

이벤트 루프는 실행 가능한 Task가 없을 때까지 이 과정을 반복하며, 모든 Task가 완료되면 루프를 종료합니다.

예시 코드 :동시 네트워크 요청

다음 코드는 이벤트 루프가 두 개의 async 함수 ($\texttt{task1}$과 )를 어떻게 동시 (Concurrency)에 실행하는지 보여줍니다.

import asyncio
import time

async def task1():
    print("Task 1 시작")
    await asyncio.sleep(2)  # 2초 동안 I/O 대기 상태 (제어권 양보)
    print("Task 1 완료")

async def task2():
    print("Task 2 시작")
    await asyncio.sleep(1)  # 1초 동안 I/O 대기 상태 (제어권 양보)
    print("Task 2 완료")

async def main():
    start = time.time()
    # 두 코루틴을 Task로 만들어 동시에 실행하도록 스케줄링
    await asyncio.gather(task1(), task2())
    end = time.time()
    print(f"총 실행 시간: {end - start:.2f}초")

# 이벤트 루프를 실행하고 main 코루틴을 시작
if __name__ == "__main__":
    # Python 3.7+에서는 이 한 줄로 충분합니다.
    asyncio.run(main()) 

# 출력 결과:
# Task 1 시작
# Task 2 시작
# Task 2 완료
# Task 1 완료
# 총 실행 시간: 2.xx초 (두 작업 중 가장 긴 2초에 가까움)

 

코드해설

  1. task1이 실행되고 $\texttt{asyncio.sleep(2)}$에서 제어권을 이벤트 루프에 양보합니다.
  2. 이벤트 루프는 즉시 task2를 실행하고, $\texttt{asyncio.sleep(1)}$에서 task2 역시 제어권을 양보합니다. 
  3. 1초 후 $\texttt{task2}$의 대기가 끝나고 Task2 완료를 출력합니다.
  4. 2초 후 $\texttt{task1}$의 대기가 끝나고 Task1 완료를 출력합니다.
  5. 만약 동기적으로 실행했다면 총 3초가 걸렸겠지만, 이벤트 루프 덕분에 가장 긴 작업 시간인 약 2초만에 두 작업이 완료됩니다.

파이썬이벤트 루프의 장단점 및 활용처

왜 사용하는 지 & 언제 사용하는지

파이썬에서 이벤트 루프를 사용하는 주된 이유는 I/O 성능 향상과 자원 효율성입니다. 

  • I/O 바운드 작업 : 웹 스크래핑, 대규모 네트워크 서버(웹 서버, 채팅 서버), 데이터베이스 쿼리 등 대부분의 시간이 데이터를 기다리는 데 소요되는 작업에 필수적입니다.
  • 자원 효율성 : 멀티스레딩/멀티프로세싱은 스레드/프로세스 생성 및 관리(오버헤드) 비용이 크지만, $\texttt{asyncio}$는 하나의 스레드 내에서 문맥 전환(Context Switching) 비용이 적은 코루틴을 사용해 메모리를 절약하고 효율을 높입니다.
장점 단점
높은 처리량 : 단일 스레드로 수천 개의 동시 연결(Concurrent Connections)을 처리할 수 있어, 네트워크 서버에 이상적입니다. CPU 바운드 작업에 취약 : 이벤트 루프는 단일 스레드이므로, 긴 CPU 연산 코드가 실행되면 전체 루프가 멈춰버립니다.
경량성 : 스레드보다 가벼운 코루틴을 사용해 메모리 오버헤드가 적습니다. 비동기 전염성 : 한 번 async 코드를 사용하기 시작하면, 그 코드를 호출하는 모든 상위 함수도 $\texttt{async}$여야 합니다.
명시적인 제어권 양보 : 개발자가 $\texttt{await}$를 통해 제어권 양보 시점을 명확히 알 수 있습니다. 디버깅 복잡성 : 동기 코드에 익숙한 경우, 코루틴의 상태 변화와 실행 흐름을 추적하기 어려울 수 있습니다. 

 

활용 분야

  • 고성능 웹 프레임워크 : FastAPI, Starlette(qlehdrl dnpq tjqj)
  • 비동기 데이터베이스 연결 : asyncpg, aiomysql (데이터베이스 I/O 대기 시간 최소화)
  • 웹 소켓 및 채팅 서버 : 실시간 양방향 통신 처리
  • 다중 웹 API 호출 : $\texttt{aiohttp}$를 사용한 대량의 외부 API 요청 동시 처리

 

반응형