Search

Thread Pool vs Event loop

FastAPI는 스레드 풀에서 동기 경로를 실행하여 메인 이벤트 루프에서 분리합니다.
"쓰레드 풀"과 "이벤트 루프"의 개념과 FastAPI가 이를 어떻게 활용하는지 명확히 하기 위해 FastAPI와 같은 웹 프레임워크에서 각 개념과 응용 프로그램에 대해 자세히 알아보겠습니다.

이벤트 루프

이벤트 루프(event loop)는 프로그램이 이벤트나 메시지를 대기하고 처리하는 프로그래밍 구조입니다. I/O(Input/Output) 작업이 완료되기를 기다리는 동안에도 프로그램이 다른 작업을 수행할 수 있게 하는 비동기 프로그래밍의 핵심 요소입니다. 이벤트 루프는 I/O 작업이 완료될 때까지 해당 작업을 "일시 중지"하고, 완료되면 "재개"하여 비동기 작업을 관리합니다.
이벤트 루프는 비동기 작업을 효율적으로 처리하기 위한 개념적 모델로, 소프트웨어 개발에 사용됩니다. 이는 컴퓨터의 물리적 구성 요소나 아키텍처 요소가 아닌, 특정 프로그래밍 언어와 그에 따른 런타임 환경에서 구현되는 프로그래밍 패턴입니다. 이벤트 루프를 통해 프로그램은 차단되지 않고 I/O 작업을 수행할 수 있으며, 네트워크 요청이나 파일 I/O 등의 작업이 완료되기를 기다리는 동안 다른 작업을 계속 실행할 수 있습니다.
비동기 프로그래밍을 지원하는 언어의 런타임 환경은 이벤트 루프를 포함합니다. 예를 들어 자바스크립트에서는 브라우저의 자바스크립트 실행 엔진과 Node.js 런타임 환경에 이벤트 루프가 포함되어 있습니다. 파이썬에서는 asyncio 라이브러리가 이벤트 루프 기능을 제공합니다.
이벤트 루프는 비동기 연산을 처리하기 위한 개념적 모델로 시작되었지만, 비동기 프로그래밍을 지원하는 프로그래밍 언어와 프레임워크에서 매우 실용적으로 구현할 수 있습니다. 이러한 구현은 작업의 실행, 예약, 이벤트 또는 메시지의 배포를 효율적으로 관리하도록 설계되었습니다.
이벤트 루프의 기본 개념은 기본 실행 스레드를 차단하지 않고 작업을 관리하는 것으로, 이를 지원하는 언어 간에 일관성이 있습니다. 하지만 구현 세부 사항은 언어와 런타임 환경에 따라 다를 수 있습니다. 예를 들어, 이벤트 루프가 자바스크립트(Node.js 및 브라우저)에서 작동하는 방식은 Python의 asyncio 또는 다른 비동기 프로그래밍을 지원하는 언어에서의 구현 방식과 다를 수 있습니다.

스레드 풀

스레드 풀은 태스크를 실행하는 데 사용할 수 있는 미리 초기화된 스레드의 모음입니다. 리소스 집약적일 수 있는 각 작업에 대해 새 스레드를 시작하는 대신 이 풀의 스레드를 재사용하여 작업을 수행합니다. 이 접근 방식은 여러 작업을 병렬로 처리하는 데 유용하며, 특히 이러한 작업이 차단되어 이벤트 루프의 실행을 방해할 수 있습니다.
FastAPI는 스레드 풀을 사용한다는 점에서 고유하지 않습니다. 다른 많은 웹 프레임워크와 프로그래밍 환경에서도 비슷한 이유로 스레드 풀을 사용합니다.
Node.js:
주로 비블록킹, 이벤트 중심 아키텍처로 유명하지만 Node.js는 CPU 집약적인 작업을 실행하거나 메인 이벤트 루프와 병렬로 I/O 작업을 차단하기 위해 작업자 스레드(스레드 풀의 한 형태)를 사용할 수 있습니다.
장고(ASGI 서버 포함):
비동기 기능을 위해 동기 프레임워크인 장고를 ASGI 서버에서 실행할 수 있습니다.
이렇게 하면 동기화 작업(뷰 또는 미들웨어 등)을 스레드 풀로 오프로드하여 비동기 이벤트 루프를 차단하지 못하게 할 수 있습니다.
플라스크(구니콘 포함):
플라스크는 전통적으로 동기식 틀입니다.
그러나 gevent 또는 eventlet 작업자로 구성된 Gunicorn과 같은 WSGI 서버와 함께 제공되면 요청을 동시에 처리할 수 있으며 이벤트 루프의 동작을 모방하고 병렬 실행을 위해 녹색 스레드(경량 스레드)를 사용합니다.
동시 작업을 관리하기 위한 스레드 풀에 의존하지 않는 프레임워크 및 프로그래밍 환경, 특히 완전히 비동기식으로 처음부터 설계된 환경이 있습니다. 이러한 프레임워크는 일반적으로 메인 실행 흐름을 차단하지 않고 I/O 작업을 포함한 모든 작업을 처리하기 위해 이벤트 루프를 활용하는 이벤트 기반 모델을 사용합니다. 그러나 이러한 환경에서도 효율적으로 관리할 수 없는 작업을 비차단 방식으로 처리할 수 있는 조항이 있을 수 있습니다.

프레임워크의 예와 동시성에 대한 접근방법

Node.js는 스레드 풀을 주요 작업에 사용하지 않는 런타임 환경입니다. 이는 이벤트 중심의 비차단 I/O 모델에 기반을 두고 만들어졌습니다. 그러나 CPU 집중적인 작업이나 비차단 방식으로 효율적으로 관리하기 어려운 경우에는 Node.js에서 Worker Threads 모듈을 제공합니다. 이 모듈을 통해 개발자들은 자바스크립트를 실행하는 작업자 스레드 풀을 병렬로 만들 수 있습니다. 이를 통해 비차단 모델과 잘 맞지 않는 작업들을 처리할 수 있습니다.
Tornado는 Python 웹 프레임워크 및 비동기 네트워킹 라이브러리로, 많은 수의 네트워크 연결을 처리할 수 있도록 설계되었습니다. 이는 비차단형으로 제작되었으며, 이벤트 루프를 사용하여 모든 작업을 관리합니다. Tornado 자체는 코어 작업을 위해 스레드 풀에 의존하지 않지만, 스레드 풀과 통합할 수 있는 인터페이스를 제공합니다. 이에는 concurrent.futures.ThreadPoolExecutor가 포함되며, 이는 이벤트 루프 내에서 비동기적으로 처리할 수 없는 차단 작업을 오프로드하는 것을 목적으로 합니다.
Sanic은 비동기/대기 구문의 원리를 활용하여 비동기 핸들러를 정의하는 또 다른 파이썬 웹 프레임워크입니다. Python의 힘을 활용하여 빠르고 차단되지 않도록 설계되었습니다. Sanic은 기본적으로 동시성 관리를 위해 asyncio를 사용하며, 스레드 풀을 작업에 사용하지는 않지만, 개발자들이 이벤트 루프를 차단하지 않는 방식으로 동기 코드를 실행할 수 있도록 허용합니다. 이를 위해 실행기 스레드에서 차단 기능을 실행하는 기능을 제공합니다.
이벤트 중심의 비차단 I/O 모델을 중심으로 제작되었습니다.
그러나 CPU 집약적이고 비블록 방식으로 효율적으로 관리할 수 없는 작업의 경우 Node.js는 Worker Threads 모듈을 제공합니다.
이 모듈을 사용하면 개발자는 자바스크립트를 실행하기 위한 작업자 스레드 풀을 병렬로 만들 수 있으므로 비블록킹 모델과 잘 맞지 않는 작업을 처리할 수 있는 방법을 제공합니다.

스레드 풀 구현: 예제

프레임워크에서 스레드 풀을 제공하지 않지만 특정 작업에 필요한 환경에서는 언어별 라이브러리를 사용하여 개발자가 자신의 스레드 풀을 구현할 수 있습니다. 예를 들어, 파이썬에서는 concurrent.futures 모듈은 호출 가능한 것을 비동기적으로 실행하기 위한 높은 수준의 인터페이스를 제공합니다.
Python에서 스레드 풀을 사용하는 간단한 예 concurrent.futures.ThreadPoolExecutor:
import concurrent.futures import time # A function that simulates a blocking operation def blocking_task(name): print(f"Task {name} started") time.sleep(2) # Simulate a blocking I/O operation returnf"Task {name} completed" # Using ThreadPoolExecutor to execute several tasks in parallel with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: futures = [executor.submit(blocking_task, name) for name in ['A', 'B', 'C']] for future in concurrent.futures.as_completed(futures): print(future.result())
Python
복사
이 예에서는, ThreadPoolExecutor 는 차단 작업의 여러 인스턴스를 병렬로 실행하는 데 사용되며, 주 프로그램 흐름을 중단하지 않고 차단 작업을 관리하기 위해 Python 응용 프로그램에서 스레드 풀을 구현하고 활용할 수 있는 방법을 보여줍니다.
요약하면, 일부 프레임워크와 환경은 완전히 비동기식 비블록킹 모델을 활용하여 스레드 풀 없이 작동하도록 설계되었지만, 차단 작업을 효율적으로 관리해야 할 경우 스레드 풀 기능을 통합하는 데 사용할 수 있는 메커니즘과 라이브러리가 있습니다.

동기화 작업을 위한 스레드 풀

FastAPI 및 동기 및 비동기 프로그래밍을 모두 지원하는 유사한 프레임워크에서는 스레드 풀을 사용하여 동기 경로를 처리합니다. I/O 작업 차단을 포함할 수 있는 동기 경로가 요청을 받으면 FastAPI는 해당 경로의 실행을 풀의 스레드로 오프로드합니다. 이 접근 방식은 동기식 작업의 차단 특성이 메인 이벤트 루프를 중지하지 않도록 하여 애플리케이션이 응답성을 유지하고 다른 비동기식 요청 및 작업을 동시에 계속 처리할 수 있도록 합니다.

비동기 연산을 위한 이벤트 루프

그러나 이벤트 루프는 비동기식 작업을 관리하는 데 사용됩니다. 응용 프로그램이 초기 작업이 완료될 때까지 기다리지 않고(예: 차단되지 않는 I/O 호출) 작업을 시작한 다음 다른 작업을 계속 실행할 수 있습니다. 작업이 준비되면(예: 네트워크 요청의 데이터를 사용할 수 있음) 이벤트 루프는 작업을 다시 픽업하고 실행을 재개합니다. 이 모델은 여러 개의 동시 작업을 차단 없이 처리할 수 있는 애플리케이션의 기능을 극대화하므로 I/O 바인딩 작업에 매우 효율적입니다.

스레드 풀 또는 이벤트 루프가 없는 프레임워크

스레드 풀 또는 이벤트 루프를 구현하지 않는 프레임워크는 일반적으로 보다 전통적인 동기 처리 모델에 의존합니다. 이 모델에서는 각 요청이 순차적으로 처리되며, 서버는 다음 요청으로 넘어가기 전에 작업이 완료될 때까지 기다려야 합니다. 이는 응답 시간을 크게 늘리고 애플리케이션의 확장 능력을 저하시킬 수 있기 때문에 특히 부하가 많이 걸리거나 장시간 작업을 처리하는 경우 비효율을 초래할 수 있습니다.
그러나 모든 애플리케이션이 이벤트 루프 또는 스레드 풀에서 제공하는 동시성 및 응답성 수준을 요구하는 것은 아닙니다. 단순한 애플리케이션이나 트래픽이 적은 애플리케이션의 경우 기존 동기화 프레임워크가 완벽하게 적합할 수 있습니다. 또한 일부 프레임워크는 다중 처리 또는 백그라운드 작업 대기열과 같은 동시성 및 병렬성을 위한 다른 메커니즘을 제공하므로 특정 조건에서 성능과 응답성을 향상시키는 데 도움이 될 수도 있습니다.