카테고리 없음

이벤트 루프 vs 쓰레드풀 매커니즘 비교

재삐신생 2025. 1. 6. 12:12
반응형

이벤트 루프(Event Loop)와 쓰레드 풀(Thread Pool)은 병렬 작업과 동시성을 처리하는 두 가지 주요 매커니즘입니다.

이들은 각각 다른 설계 철학과 구현 방식을 기반으로 동작하며, 사용 사례에 따라 장단점이 있습니다.

아래에서 두 방식의 작동 원리와 차이점을 비교하여 설명하겠습니다.


1. 이벤트 루프(Event Loop)

작동 원리

  • 단일 쓰레드 기반:
    • 이벤트 루프는 하나의 쓰레드에서 모든 작업을 처리합니다.
    • 작업이 블로킹되지 않도록 비동기적 방식으로 요청을 처리합니다.
  • 작업 큐(Task Queue):
    • 작업 요청이 들어오면 작업 큐에 등록됩니다.
    • 이벤트 루프는 큐에 저장된 작업을 하나씩 가져와 처리합니다.
  • 비동기 I/O:
    • I/O 작업(네트워크, 파일 등)을 비동기로 실행하여 완료 시 콜백이나 프라미스(약속된 작업 결과)를 통해 알립니다.
  • Non-blocking 모델:
    • I/O 작업이 완료될 때까지 이벤트 루프는 다른 작업을 계속 처리합니다.

구성 요소

  1. 이벤트 큐(Event Queue):
    • 처리할 작업이 등록되는 큐.
    • 예: 함수 호출, 네트워크 응답, 타이머 이벤트.
  2. 이벤트 디스패처:
    • 이벤트 큐에서 작업을 가져와 실행.
  3. 비동기 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 바운드 작업을 병렬로 처리합니다.
  • 컨텍스트 스위칭:
    • 각 쓰레드가 독립적으로 작업을 처리하며, 컨텍스트 스위칭이 발생.

구성 요소

  1. 쓰레드 풀(Thread Pool):
    • 미리 생성된 쓰레드 집합.
  2. 작업 대기열(Work Queue):
    • 처리 대기 중인 작업이 저장되는 큐.
  3. 스케줄러:
    • 쓰레드가 사용 가능해지면 작업 대기열에서 작업을 할당.

예제: 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 중심의 작업에는 쓰레드 풀이 더 적합합니다.

필요한 작업 부하와 처리 성능을 기반으로 선택하거나 혼합 접근법을 고려하는 것이 중요합니다.

반응형