반응형
이벤트 루프(Event Loop)와 쓰레드 풀(Thread Pool)은 병렬 작업과 동시성을 처리하는 두 가지 주요 매커니즘입니다.
이들은 각각 다른 설계 철학과 구현 방식을 기반으로 동작하며, 사용 사례에 따라 장단점이 있습니다.
아래에서 두 방식의 작동 원리와 차이점을 비교하여 설명하겠습니다.
1. 이벤트 루프(Event Loop)
작동 원리
- 단일 쓰레드 기반:
- 이벤트 루프는 하나의 쓰레드에서 모든 작업을 처리합니다.
- 작업이 블로킹되지 않도록 비동기적 방식으로 요청을 처리합니다.
- 작업 큐(Task Queue):
- 작업 요청이 들어오면 작업 큐에 등록됩니다.
- 이벤트 루프는 큐에 저장된 작업을 하나씩 가져와 처리합니다.
- 비동기 I/O:
- I/O 작업(네트워크, 파일 등)을 비동기로 실행하여 완료 시 콜백이나 프라미스(약속된 작업 결과)를 통해 알립니다.
- Non-blocking 모델:
- I/O 작업이 완료될 때까지 이벤트 루프는 다른 작업을 계속 처리합니다.
구성 요소
- 이벤트 큐(Event Queue):
- 처리할 작업이 등록되는 큐.
- 예: 함수 호출, 네트워크 응답, 타이머 이벤트.
- 이벤트 디스패처:
- 이벤트 큐에서 작업을 가져와 실행.
- 비동기 I/O 핸들러:
- 비동기 작업 완료 시 결과를 처리.
예제: Node.js
Node.js는 이벤트 루프 기반의 대표적인 런타임입니다.
const fs = require('fs');
console.log("Start");
fs.readFile('example.txt', (err, data) => {
if (err) throw err; console.log("File read completed");
});
console.log("End");
출력 결과:
Start
End
File read completed
- fs.readFile은 비동기로 동작하며, 파일 읽기가 완료되면 콜백이 실행됩니다.
- 이벤트 루프는 파일 읽는 동안 다른 작업(console.log("End"))을 계속 처리합니다.
장점
- 효율적 자원 사용:
- 단일 쓰레드로 많은 요청 처리 가능.
- 높은 동시성:
- I/O 바운드 작업에 적합.
- 비동기 프로그래밍 지원:
- I/O 작업이 완료될 때까지 대기하지 않음.
단점
- CPU 바운드 작업 부적합:
- 단일 쓰레드가 작업을 처리하므로 CPU 집약적인 작업이 지연을 초래.
- 복잡한 코드 관리:
- 콜백 헬(callback hell)이나 비동기 코드 디버깅이 어려울 수 있음.
2. 쓰레드 풀(Thread Pool)
작동 원리
- 다중 쓰레드 기반:
- 미리 생성된 일정 수의 쓰레드로 작업을 병렬 처리합니다.
- 작업 큐(Task Queue):
- 요청이 들어오면 작업 큐에 저장됩니다.
- 쓰레드가 사용 가능하면 작업 큐에서 작업을 가져와 실행합니다.
- 동기 및 비동기 작업 모두 지원:
- CPU 바운드 및 I/O 바운드 작업을 병렬로 처리합니다.
- 컨텍스트 스위칭:
- 각 쓰레드가 독립적으로 작업을 처리하며, 컨텍스트 스위칭이 발생.
구성 요소
- 쓰레드 풀(Thread Pool):
- 미리 생성된 쓰레드 집합.
- 작업 대기열(Work Queue):
- 처리 대기 중인 작업이 저장되는 큐.
- 스케줄러:
- 쓰레드가 사용 가능해지면 작업 대기열에서 작업을 할당.
예제: Java의 ExecutorService
Java는 쓰레드 풀 관리를 위해 ExecutorService를 제공합니다.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
Runnable task = () -> {
System.out.println("Task executed by " + Thread.currentThread().getName());
};
for (int i = 0; i < 5; i++) {
executor.submit(task);
}
executor.shutdown();
}
}
출력 예:
Task executed by pool-1-thread-1
Task executed by pool-1-thread-2
Task executed by pool-1-thread-1
...
장점
- 병렬 처리:
- CPU 바운드 작업에 적합하며, 동시성 처리 성능이 뛰어남.
- 작업 격리:
- 각 작업이 독립적인 쓰레드에서 처리되므로 블로킹 문제가 제한적.
- 쓰레드 재사용:
- 쓰레드를 재사용하여 쓰레드 생성 오버헤드 감소.
단점
- 컨텍스트 스위칭 비용:
- 다수의 쓰레드가 활성화되면 컨텍스트 스위칭으로 인한 CPU 오버헤드 발생.
- 높은 메모리 소비:
- 쓰레드가 많아질수록 스택 메모리와 관련 자원이 증가.
- 쓰레드 풀 크기 제한:
- 풀 크기를 초과하는 요청은 대기 상태로 들어가 지연 발생.
3. 이벤트 루프 vs 쓰레드 풀 비교
특성이벤트 루프쓰레드 풀
처리 방식 | 단일 쓰레드, 비동기 이벤트 처리 | 다중 쓰레드, 병렬 처리 |
적합한 작업 유형 | I/O 바운드 작업 | CPU 바운드 작업 및 동기 처리 |
자원 사용 | 적은 메모리와 CPU 자원 사용 | 많은 메모리와 컨텍스트 스위칭 오버헤드 |
확장성 | 높은 동시성 처리 | 쓰레드 풀 크기에 따라 제한적 |
코드 복잡성 | 비동기 코드 관리 필요 | 동기 코드로 간단한 관리 가능 |
블로킹 문제 | 하나의 작업이 블로킹되면 전체 영향을 받음 | 작업 간 블로킹이 독립적 |
4. 실제 사례에서의 선택 기준
- 이벤트 루프:
- I/O 바운드: 네트워크 서버(Node.js, Nginx), 파일 처리 등.
- 많은 동시성을 요구하지만 작업 자체가 비교적 가벼운 경우.
- 쓰레드 풀:
- CPU 바운드: 복잡한 계산, 이미지 처리, 데이터 변환 등.
- 동시 요청 수가 상대적으로 적고 작업 시간이 긴 경우.
5. 혼합 접근 방식
현대적인 서버나 애플리케이션에서는 이벤트 루프와 쓰레드 풀을 혼합하여 사용하기도 합니다.
- 예: Node.js는 이벤트 루프를 기본으로 사용하되, CPU 바운드 작업은 libuv의 쓰레드 풀을 통해 처리합니다.
- 예: Netty는 이벤트 루프를 통해 네트워크 작업을 처리하고, 워커 쓰레드 풀을 사용해 병렬 작업을 처리합니다.
결론
이벤트 루프와 쓰레드 풀은 각각 다른 종류의 작업 부하와 성능 요구에 최적화되어 있습니다.
I/O 중심의 작업에는 이벤트 루프가, CPU 중심의 작업에는 쓰레드 풀이 더 적합합니다.
필요한 작업 부하와 처리 성능을 기반으로 선택하거나 혼합 접근법을 고려하는 것이 중요합니다.
반응형